1. Jexxa Modules

Jexxa is split into some modules so that you can define the dependencies of your application on a fine-grained basis.

Table 1. Describes the modules of Jexxa.

Module

Description

Jexxa-Core

Provides the core functionality of Jexxa as described in Implementing main. In case you write a new business application, you will need this package.

In addition, it provides the following sub-packages that can be used by your application:

  • drivingadapter: Provides ready-to-use driving adapter to receive and forward incoming requests to the application core. Included driving adapter in this package are Scheduler and JMSAdapter.

  • infrastructure: Provides implementation of typical application infrastructure patterns that simplify the implementation of driven adapters. Provided implementations are JDBCKeyValueRepository, JDBCObjectStore, JMSSender, and TransactionalOutboxSender

  • common: Provides wrapper to standard java libraries used by Jexxa. The Main focus is to simplify and reduce these APIs to the requirements of Jexxa.

Jexxa-Web

Provides driving adapters to access your application via HTTP. The used web-framework is javalin, that internally uses jetty.

  • Driving-Adapter: RESTfulRPCAdapter

Jexxa-Test

Supports writing unit-, stub-, and integration-tests for your business application.

2. Application Development

The application development using Jexxa is quite easy. The required steps are described in this section.

2.1. Implementing main

The JexxaMain class provides a convenient way to bootstrap your application. The main focus here is to make the entry points of your application core as well as the flow of control visible.

  1. In main create an instance of JexxaMain.

  2. Bind driving adapters to inbound ports with JexxaMain.bind(<DrivingAdapter>.class).to(<port>.class).

  3. Startup your application using JexxaMain.run().

A simple example can be seen in tutorial HelloJexxa.

2.2. Implementing Infrastructure Binding

To implement your outbound ports either by using Jexxa’s infrastructure components or any other third party library. Jexxa provides strategies for persisting or sending data that can be used in the implementation of your outbound ports.

A simple example can be seen in tutorial BookStore.

2.3. BoundedContext

As described in the architecture of Jexxa, this framework has a strong focus on Domain-Driven Design. Therefore, each application provides a so called BoundedContext object which provides status information about the running application.

So this object provides important information for your ops-team. Therefore, it is recommended to expose this class by any driving adapter.

  1. BoundedContext.isRunning(): This method returns true only if JexxaMain.run() was successfully executed. So you can use this method in your ops-environment such as docker or kubernetes to check if the application or a new version of it can be correctly started.

  2. BoundedContext.isHealthy(): Jexxa provides monitoring components which report about the healthy status of the application. A simple example could be a TimerMonitor that informs about that no requests have been received for a longer period of time.

  3. BoundedContext.diagnostics(): This method provides human-readable information from the established health indicators.

3. Application Configuration

Jexxa allows you to externalize your configuration. This allows you to run the same application in different environments. By default, Jexxa uses properties files. In addition, you can set system properties to externalize the configuration. Jexxa considers properties in the following order:

  1. Properties directly passed to JexxaMain

  2. Java System properties (System.getProperties())

  3. Imported properties using parameter -Dio.jexxa.config.import from inside or outside the jar archive

  4. Property file jexxa-application.properties inside the jar archive

3.1. Properties files

Jexxa uses a hierarchical approach so that an imported properties file overwrites values in jexxa-application.properties. This allows you to make differences between production and other environments more transparent.

All Jexxa tutorials provide the three properties files described below.
  1. Default configuration: By default, Jexxa loads jexxa-application.properties (if available) and provides included properties to all infrastructure components. This property file is loaded as soon as you create a JexxaApplication. So this file should be used for your production environment.

  2. Testing: When using JexxaTest, the properties jexxa-test.properties is loaded (if available) and overwrites the default configuration provided in jexxa-application.properties. So this properties file should only include differences to the production environment so that you can run your application on your local machine for testing purpose. Typically, these are URLs to infrastructure services, such as database, as well as the corresponding credentials.

  3. Local: Sometimes it is required to run your application without any infrastructure service. For example, this could be required for rapid prototyping. In this case you can provide a properties file that defines explicit driven adapters or disables the driving adapter.

3.2. Secrets

One of the most crucial aspects with productive systems is the handling of secrets such as usernames, passwords, or private keys. Jexxa addresses this problem with two approaches.

  • First, you can provide all secrets unencrypted as properties which is only recommended for developing on your local machine. For example, you can use io.jexxa.jdbc.password to define the password for your JDBC connection.

  • Secondly, you can provide these properties with a file. In this case, you have to use io.jexxa.jdbc.file.password. This approach enables a seamless integration of security mechanisms from virtualization environments such as docker-swarm and is recommended for all productive systems.

4. Conventions

Jexxa defines some conventions for realizing implicit constructor injection which are described here. In addition, driving adapter can define their own conventions to automatically expose ports via a specific technology stack. Within Jexxa, these driving adapter are called generic driving adapter. In the rest of this Section you find a description of conventions used by these generic driving adapter.

4.1. Package Structure

Jexxa assumes following package structure by default, which is quite common in DDD community:

  • applicationservice: Provides interfaces per application scenario

  • domainservice: Provides specialized domain logic that cannot be attributed to a single entity or value object within the domain.

  • domain: Provides the core domain typically grouped by use cases.

    • <use case 1>: Each use case consists of the related Aggregates, Repository interfaces, ValueObjects and DomainEvents.

    • …​

    • <use case n>

  • infrastructure

    • drivenadapter: Provides the implementation of outbound ports typically with sub packages for each technology stack such as messaging or persistence.

    • drivingadapter: Provides the implementation of so-called port adapters outbound ports typically with sub packages for each technology stack such as messaging.

If you want to use your own package structure you have to tell Jexxa which parts belong to the application core and which one to the infrastructure. For this purpose, you can use methods JexxaMain::addToApplicationCore or JexxaMain::addToInfrastructure respectively.

4.2. Dependency Injection (DI)

Jexxa provides a simple DI mechanism to instantiate inbound ports of a business application and to inject required dependencies. Within Jexxa we only support implicit constructor injection as explained here.

Table 2. Conventions used to realize implicit constructor injection.

Components

Conventions

Reason

Inbound Port

  1. A single public constructor.

  2. Parameters of the constructor must be interfaces of required outbound ports.

  3. A unique implementation of each interface exists in the infrastructure of your application.

  1. Avoids ambiguity when choosing the constructor.

  2. Ports should be self-contained as much as possible. Therefore, only outbound ports should be hand in. This ensures that the infrastructure is agnostic to domain logic and knows only the interfaces required by the application core but not any other inner components.

  3. Each package should only include a single implementation of a specific interface to achieve the common-closure principle.

Outbound Port

Not applicable

Outbound ports are interfaces

Driven Adapter

  1. Only a single driven adapter implements a specific outbound port.

  2. One of the following constructors must be available (checked in this order):

    1. Public default constructor

    2. Public constructor with a single Properties attribute

    3. Public static factory method that gets no parameters and returns the type of the Outbound Port (and not the type of the driven adapter)

    4. Public static factory method with a single Properties parameter and returns the type of the outbound port (and not the type of the driven adapter).

  1. Avoids ambiguity when choosing a Driven Adapter. At the moment you can only limit the search space of driven adapters on a package level.

  2. Using constructors or factory methods do not require any special annotations.

A driven adapter gets the same Properties object as JexxaMain.

Port Adapter

One of the following constructors must be available (checked in this order):

  1. A single public constructor with a single attribute. The attribute is the concrete type of concrete port, such as MyDrivingAdapter(ConcretePort conrectePort).

  2. A single public constructor with two attributes. The first attribute is the concrete type of specific port, such as MyDrivingAdapter(ConcretePort conrectePort, Properties properties).

  1. A port adapter is tightly coupled to a specific port. Therefore, it gets its concrete type injected.

Driving Adapter

One of the following constructors must be available (checked in this order).

  1. Public Default constructor

  2. Public constructor with a single Properties attribute

  3. Public static factory method without parameters and returns the type of the driving adapter

  4. Public static factory method with a single Properties parameter and returns the type of the requested driving adapter

Using constructors or factory methods do not require any special annotations. Using Properties is a standard approach in Java to provide configuration information.

A driving adapter gets the same Properties object as JexxaMain.
Constructor vs. static factory method: In most cases implementing a constructor is the preferred approach when realizing an adapter. Using a static factory method is only recommended if the adapter needs special or more complex configuration which should be done before creating the adapter itself.

4.3. JSON representation

4.3.1. JSON representation of Exceptions

In case you (de)serialize an exception, Jexxa (de)serialize following information

Table 3. Describes the JSON representation of an Exception.

JSON value

Description

message

Includes the message text from the exception, if available.

cause

Includes the message information from including cause object, if available.

Jexxa does not serialize any other information such as stack trace due to security reason.

4.3.2. JSON representation of Date and Time

JSON does not specify a specific date representation. JavaScript uses ISO 8601 string format to encode dates as a string, which is a common way to exchange date information between different systems.

Java8 introduces a new API for date and time for good reason. Even if there is a reason to use the old API you should not use it as part of your exposed API of the application. These two aspects leads to following design decision:

RESTfulRPCAdapter supports only Java8 Date API and represents a date as JSON-string in ISO 8601 string format.
Table 4. Describes the JSON representation of Java date.

Java type

JSON

LocalDate

Is mapped to a JSON-string representing a date without any time information.

Example: "2020-11-29"

LocalDateTime

Is mapped to a JSON-string representing a date including timezone information to avoid confusion within a distributed system.

Example: "2020-11-29T06:36:36.978Z"

ZonedDateTime

Is mapped to a JSON-string representing a date with timezone information.

Example: "2020-11-29T06:36:36.978Z"

If ou need some example how RESTfulRPCAdapter maps Java’s Date and Time API to JSON please see here.

4.4. Conventions of RESTfulRPCAdapter

The RESTfulRPCAdapter is a simple RPC mechanism utilizing REST. Based on the REST maturity model from Richardson, it is REST on level 0. So this adapter is using HTTP as a transport mechanism for remote interactions, but without using any of the mechanisms of the web. When you start developing a new durable business application, this should be sufficient in most cases. Only when the partitioning of the business domain to the application core is stable, you should think about how to offer it via REST at a higher maturity level.

When you start using this adapter, I recommend enabling OpenAPI support at least during development as described here. It simplifies understanding the REST API.

This adapter uses the following conventions:

Table 5. Describes the conventions used in RESTfulRPCAdapter.

RESTfulRPCAdapter

Convention

URI

URIs for Java objects are generated as follows for all public methods:

This implies the following consequences:
  • Simple-name of a class must be unique within a single application.

  • Each class must have unique method names. Any method overloading is not supported.

  • Methods from base class Object are ignored.

HTTP-GET

All public non-static Methods of an object are exposed via HTTP GET if the following conditions are fulfilled:

  • Return type is not void AND

  • Method has no attributes

HTTP-POST

All public non-static methods of an object are exposed via HTTP POST if they are not exposed as HTTP-GET. The conditions are:

  • Return type is void OR

  • Method has attributes

HTML Header

Content-type: application/json

HTML Request: Method attributes

  • Json object in case of a single attribute.

  • For multiple attribues to approaches are possible:

    1. Json array for multiple attributes. The order of the attributes in the Json array must match to the order of attributes of the method!

    2. A single Json object including the parameters with key arg0 to argN

  • All attributes are treated as in values in terms of RPC. This means that they are not included in the HTML response.

HTML Response: Return value

  • HTTP status code: 200

  • Return value as a single Json object

HTML Response: Exception

  • HTTP status code: 400

  • Return value as a single Json object including the following properties:

    • ExceptionType: Full type name of the exception.

    • Exception: Json representation of the exception.

HTML Response code: URI not found

  • HTTP status code: 404

5. Configuration

Some driving adapter cannot automatically expose a port via conventions. These driving adapters are called specific driving adapters. A specific driving adapter is required for integrating technology stacks that require a mapping to the interface of a port. Typical examples are mapping a RESTfulHTTP API to the public interface of a java object, or mapping an asynchronous message to a specific method of an object.

In Jexxa we split this kind of driving adapter into two parts:

  • The specific driving adapter provides the reusable part of the driving adapter, such as connecting to a messaging system or listening on a network port.

  • The port adapter must be implemented by the application developer and describes how to map incoming requests to the used port.

To describe the mapping of the port adapter within Jexxa, we recommend using Java annotations for the following reason:

  • Using annotations between a specific driving adapter and port adapter is not a problem because these two components belong to the infrastructure and are tightly coupled.

  • The configuration is documented within the source code so that it is directly visible to the developer.

  • The configuration can only be changed during compile time. This is a conscious restriction of the configuration options to ensure that the development environment must be identical with the production environment.

5.1. JMSAdapter

When implementing a port adapter for JMSAdapter, you have to implement the MessageListener interface of JMS and annotate the class with @JMSConfiguration.

Here you have to provide the following information:

Table 6. Describes the configuration used in JMSAdapter.

Configuration

Description

destination

A Required parameter describing the name of the jms topic or queue.

messagingType

A required parameter which must be either TOPIC or QUEUE depending on the used messaging type.

selector

An optional parameter which defines a message selector to filter messages.

sharedSubscriptionName

Defines name of a shared subscription so that multiple instances of your application can process incoming requests

durable

Defines if the topic or queue remains if the application is not running.

Please check the tutorial TimeService for an example.

5.1.1. Predefined MessageListener

In addition, Jexxa provides the following predefined `MessageListener`for your convenience:

  • JSONMessageListener: Can be used to receive JSON data. The base class converts the content of a received text- or byte message into JSON and provides additional convenience methods.

  • TypedMessageListener: Can be used to automatically deserialize received JSON data into a Java object that is defined within your application.

  • IdempotentListener: Ensures that duplicate messages are not forwarded to your application core. For this purpose, the message header must include a unique ID as string. By default, this listener uses the field domain_event_id which is also used by the TransactionOutboxSender.