1. Jexxa Modules
Jexxa is split into some modules so that you can define the dependencies of your application on a fine-grained basis.
Module |
Description |
Jexxa-Core |
Provides the core functionality of Jexxa as described in Implementing In addition, it provides the following sub-packages that can be used by your application:
|
Jexxa-Web |
Provides driving adapters to access your application via HTTP. The used web-framework is javalin, that internally uses jetty.
|
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.
-
In
main
create an instance ofJexxaMain
. -
Bind driving adapters to
inbound ports
withJexxaMain.bind(<DrivingAdapter>.class).to(<port>.class)
. -
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.
-
BoundedContext.isRunning()
: This method returnstrue
only ifJexxaMain.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. -
BoundedContext.isHealthy()
: Jexxa provides monitoring components which report about the healthy status of the application. A simple example could be aTimerMonitor
that informs about that no requests have been received for a longer period of time. -
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:
-
Properties directly passed to
JexxaMain
-
Java System properties (
System.getProperties()
) -
Imported properties using parameter
-Dio.jexxa.config.import
from inside or outside the jar archive -
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. |
-
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 aJexxaApplication
. So this file should be used for your production environment. -
Testing: When using
JexxaTest
, the propertiesjexxa-test.properties
is loaded (if available) and overwrites the default configuration provided injexxa-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. -
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 asmessaging
orpersistence
. -
drivingadapter
: Provides the implementation of so-called port adapters outbound ports typically with sub packages for each technology stack such asmessaging
.
-
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.
Components |
Conventions |
Reason |
||
Inbound Port |
|
|
||
Outbound Port |
Not applicable |
Outbound ports are interfaces |
||
Driven Adapter |
|
|
||
Port Adapter |
One of the following constructors must be available (checked in this order):
|
|
||
Driving Adapter |
One of the following constructors must be available (checked in this order).
|
Using constructors or factory methods do not require any special annotations. Using
|
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
JSON value |
Description |
|
Includes the message text from the exception, if available. |
|
Includes the message information from including |
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.
|
Java type |
JSON |
|
Is mapped to a JSON-string representing a date without any time information. Example: "2020-11-29" |
|
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" |
|
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:
RESTfulRPCAdapter |
Convention |
||
URI |
URIs for Java objects are generated as follows for all
|
||
HTTP-GET |
All public non-static Methods of an object are exposed via HTTP GET if the following conditions are fulfilled:
|
||
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:
|
||
HTML Header |
Content-type: application/json |
||
HTML Request: Method attributes |
|
||
HTML Response: Return value |
|
||
HTML Response: Exception |
|
||
HTML Response code: URI not found |
|
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:
Configuration |
Description |
|
A Required parameter describing the name of the jms topic or queue. |
|
A required parameter which must be either |
|
An optional parameter which defines a message selector to filter messages. |
|
Defines name of a shared subscription so that multiple instances of your application can process incoming requests |
|
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 fielddomain_event_id
which is also used by the TransactionOutboxSender.