1. Vision

Jexxa is a lightweight framework to implement business applications based on a ports and adapters architecture. It allows for easily connecting technology stacks, called adapters, to interfaces of a technology-agnostic application core which are called ports. For this purpose, Jexxa offers:

  • A fluent API to connect available adapters to your application core

  • Explicit binding between ports and adapters to make the flow of control visible

  • A lightweight core which manages the lifecycle of both ports and adapters

  • A Super simple API to integrate arbitrary technology stacks as adapters

Even though Jexxa is used within lightweight business applications and microservices, it has a strong educational focus, especially on the following principles:

  • Dependency Inversion: Get an idea how to strictly separate between technology stacks and domain logic on a code basis, which is essential for durable business applications.

  • Inversion of Control(IoC): Find the required degree of IoC for your software projects. Control aspects that are technically required but not important for your business application should be hidden by a framework. The remaining required aspects should be explicitly represented.

  • Simplicity: Be aware of what technology stacks you really need to realize the customers' use cases. Usually they are much less than expected. Keep different technology stacks loosely coupled to facilitate their exchange and, even more important, to keep them up to date. Finally, prefer a convention over configuration approach to standardize the integration of technology stacks and to reduce the complexity of their configuration.

Even if you use real powerful frameworks for good reason within your application, I hope that you find Jexxa helpful in some ways.

2. Motivation

The main motivation to develop this framework comes from experiences with developing and maintaining durable software systems running up to 40 years. Here, the role of a sustainable software architecture becomes an important part to support and guide the developer team which is currently responsible for an application.

Existing frameworks such as the Spring Framework or Jakarta provide powerful features and greatly support the development of complex business applications. On the other side, developers can unintentionally misuse these frameworks from an architectural point of view. It is very difficult to maintain applications over several decades, especially when such frameworks find their way into the application core.

When developing applications with a high durability as used in production, financial or insurance sector, it is highly recommended using an architecture with a strict focus on separating technology stacks from the application core, such as ports and adapters. This is the fundament for the development of evolvable systems.

Within the application core, developers shall only use standard language features. Available frameworks shall be used to attach the latest technology stacks to the application core.

Applications without well-defined boundaries between technology stacks and application core become difficult to maintain because changes on one side could (and, in reality, will) affect the other side. Typical symptoms are:

  • Updating to a newer version of a technology stack is challenging or even impossible so that it is much safer to use the old one. Of course, new developed applications use this old version as well because your team 'trusts' it, and you avoid maintaining a zoo of different versions.

  • Adding or replacing a technology stack is challenging or even impossible, so a disruptive technology will cause your application core to become legacy as well. A common example is database technologies that have typically found their way into companies in the following chronological order: Hierarchical DBs →relational DBs →a zoo of NoSQL DBs. Because database technologies can become crucial for your business, your company employs senior-grade developers who have deep knowledge of both legacy and new technology stacks and synchronize your business data.

  • Changes on the application core itself take much longer time as in the beginning. In the end, you need senior developers who have a deep understanding of the specific business domain, the used technology stacks and the convoluted structures of the entire application.

  • In the long term, such structural issues could (and, in reality, will) affect your enterprise architecture, so neighboring systems need to be aware of and take special actions for legacy systems.

3. General Design Decisions

All considerations and design decisions were made in the context of durable business applications. Therefore, they cannot be applied to other applications without further ado.

In contrast to technology companies, the focus of business software development at companies from the production, finance or insurance sectors is generally not on the technical side. The big challenge for software developers is to understand the business domain and how to achieve added value for the customer. The essence of this knowledge is then written in domain-specific software to increase the value for the company and customers. Typically, the use of technology stacks itself does not add value as long as the business domain itself is not changed. At best, costs can be reduced.

This actually leads to a rather obvious conclusion about team structures within developing teams, which in my experience is often ignored:

  • Senior developers should focus on the application core. By working with the domain experts, they can learn sufficient domain knowledge to map the business domain to a suitable application core.

  • The intermediate developers then support the implementation of the application core.

  • Because your senior and intermediate developers focus on the application core, your junior developers must focus on the technology stack. Therefore, your technology stack or the integration of these should be as simple as possible. The easiest way to achieve this is to use very lightweight frameworks instead of general purpose frameworks

Using a lightweight framework ensures that junior developers get the time to learn the craftsmanship of software development, methods of software architecture and the domain language step by step from the intermediate and senior developers.

Unfortunately, I have often experienced in the past companies expect a graduate or junior developer to already know the craftsmanship of software engineering and methods of software architecture so that they only need to learn a business domain. Finally, he or she has applied for the position of a full-stack developer…​

Jexxa was developed to ideally support the above team structure as well as the learning process. Therefore, the development of Jexxa is very much driven by which aspects should be made explicit or hidden.

For design aspects that should be made explicit within a business application, Jexxa provides either a specific design pattern or a Fluent API. Design aspects that should be hidden in the business application are defined in Jexxa by conventions.

3.1. Visible flow of control

Most today’s frameworks bind technology stacks automatically to your application core. If at all, you have to add a new dependency and rebuild the application. Unfortunately, you hide the flow of control which makes it harder for beginners to understand an application which is based on a ports and adapters architecture. For incoming synchronous calls (RMI), the flow of control may still be obvious, but can be hard to detect for incoming asynchronous messages.

Jexxa uses explicit binding for all driving adapters together with implicit constructor injection to make the flow of control visible. The binding happens in the main-method so that it represents the unique starting point for the flow of control of your application.

Please refer to the following tutorial for more information.

3.2. IoC without annotations

Like any other framework, Jexxa takes control of part of your application core. Especially in Java, this is often done with framework-specific annotations. The downside is that these annotations tightly couple your application core to a specific technology stack. Of course, an experienced developer would introduce a facade, but this is often seen as boilerplate code for inexperienced developers.

Based on my experience, I can only recommend annotations within the application core for the following reasons:

  • Use of Java build-in annotations, such as @Deprecated.

  • Use of annotations as metadata, e.g., to annotate your classes with the used pattern language of your application core.

  • Use annotations for cross-cutting concerns on a homeopathic level. This can be useful to make the domain language more explicit by hiding methods such as equals and hash code.

Jexxa does not use annotations for all IoC aspects such as dependency injection. Instead, conventions are used.

Section Inversion of Control (IoC) describes the used conventions in detail.

3.3. Simple Interface for Driving Adapters

One of the key aspects for durable software systems is the ability to add new technology stacks. In this context, the term new means not only a new kind of technology, but more importantly, a newer version of an already used technology with breaking changes. In such a case, it often makes more sense to provide a new implementation.

Jexxa provides a super simple API that allows for the integration of arbitrary technology stacks as driving adapters.

Together with the ability of an explicit binding on an object level, this supports the following use cases:

  • Students can support your teams with the evaluation and integration of new technology stacks as part of their bachelor or master thesis.

  • The possibility to bind driving adapter on an object level allows for the integration, updating, and migration of dedicated technology stacks in the smallest possible steps.

3.4. Strategy pattern for Driven Adapters

Driven adapters belong to the infrastructure of an application. Thus, their implementation should also be as simple as possible so that they can be implemented by junior developers. Since the interface of a driven adapter belongs to the application core, Jexxa cannot provide an API. Instead, it provides so-called application infrastructure strategies to simplify the implementation of a driven adapter.

Jexxa provides implementation for typical application infrastructure strategies so that the implementation of driven adapters is just a simple facade, which maps between the API of outbound ports to corresponding API of the strategy.

This approach also enables you to train your junior developers. As an example, I will use the implementation of a repository in the sense of DDD, which manages so-called aggregates (please refer to tutorial BookStore to see the source code):

  • Regarding your business domain, your junior developers will learn at least the name of the most important business objects, because Aggregates include the business logic of this domain.

  • From a software engineering point of view, your junior developer gets familiar with the strategy design pattern.

  • From an architectural point of view, your junior developer gets familiar with the principal of dependency inversion.

  • Finally, your developers learn that they can persist data within a database without thinking about the database layout. Using a strategy pattern instead makes the database to a plugin.

As soon as your junior developers feel that they are not challenged with implementing driven adapters, give them one of the above points to study.

4. General Building Blocks

Jexxa has a strong focus on Domain Driven Design and uses a lot of terminologies from its strategic and tactical design. An application built on Jexxa provides components that belong either to the application core or to the infrastructure. The application core includes the business logic, whereas the infrastructure of an application binds required technology components to the application core.

Figure 1 shows the separation of a Jexxa application into packages, the included components, and the relationship of the components among each other.

Figure 1 shows that you can focus on your application core. The infrastructure package is just an ultra-thin facade to attach Jexxa to the application core.
JexxaComponents
Figure 1. This figure shows the separation of a typical Jexxa application into packages, the included components, and the relationship of the components among each other. Packages labeled with <<Jexxa>> and light grey background are provided by Jexxa. Packages labeled with <<Application>> have to be implemented. As you can see, you can define on a very fine-grained level which parts of Jexxa you want to use.

Table 1 describes the packages of an application based on Jexxa.

Table 1. Describes the packages of a typical application build with Jexxa.

Package

Description

ApplicationCore

This package includes your technology-agnostic business application, that is or will be implemented by the developing team.

Infrastructure

This package includes the glue code implemented by your developing team. It binds your technology-agnostic business application to concrete technology stacks. If you use Jexxa-Infrastructure this glue code is an ultra-thin facade.

Jexxa-Infrastructure

This package provides implementation of so-called application infrastructure patterns such as transactional outbox or messaging. These components are typically used to implement the application specific Infrastructure. Jexxa provides unified access to these components by utilizing the strategy pattern.

Jexxa-DrivingAdapter

This package includes the provided driving adapter of Jexxa as well as strategies for typical application infrastructure components, which simplifies the implementation of application-specific driven adapter

Jexxa-Core

This package includes the core of Jexxa and is responsible for binding the framework to the application. The functionality of this package is used via a fluent API within the main method of your application. In more detail, it provides the following functionality:

  • Bind driving adapter to the application core, or in more detail to the Inbound Ports and validate conventions as described in Dependency Injection (DI).

  • Manages the lifecycle of all components. The details are described in Section Inversion of Control (IoC).

4.1. Package ApplicationCore

The components of package ApplicationCore are:

Table 2. Describes the components of package ApplicationCore.

Components

General Description

Support by Jexxa

Inbound Ports

Inbound ports belong to the application core and provide use cases that can be started by a driving adapter. Depending on the design of your application core, a port might be an interface or a specific implementation of a set of use cases.

  • Jexxa provides implicit constructor injection for your inbound ports which is described in Dependency Injection (DI).

  • Jexxa also allows instantiating and manage ports yourself using the provided driving and driven adapters.

Outbound Ports

Outbound ports belong to the application core but only as interface. These interfaces are implemented in package Infrastructure by a driving adapter which provides access to a specific technology stack such as a database driver.

Outbound ports are 'just' interfaces that must be defined by your application core. Jexxa provides support to implement these interfaces by providing strategies for various application infrastructure patterns.

4.2. Package Infrastructure

The components of package Infrastructure are:

Table 3. Describes the components of package Infrastructure.

Components

General Description

Support by Jexxa

Driven Adapters

Driven adapters implement the outbound ports and can be injected into the inbound ports which in turn operate on these interfaces. Typically, they map domain objects to a specific technology stack.

Jexxa provides strategies providing different implementations of typical application infrastructure patterns to simplify the development of driven adapters of an application.

Port Adapters

Port adapters enable mapping between different representational styles of a specific port. For example, this is required if a port should be exposed via a RESTful API. A port adapter belongs to the infrastructure of the application and is bound to a specific driving adapter.

Providing receiving driving adapters that simplify the development of the port adapters.

4.3. Package Jexxa-Infrastructure

The components of Jexxa-Infrastructure are:

Table 4. Describes the components of package JexxaInfrastructure.

Components

General Description

Realization in Jexxa

Generic/Specific Driving Adapters

Driving adapters belong to the infrastructure and receive commands from a specific client such as a UI or a console and forwards them to connected ports.

Jexxa provides a convention and configuration approach for driving adapters.

A generic driving adapter automatically exposes methods from connected inbound ports by using a convention. For example, this can be used for an RPC mechanism.

A specific driving adapter is used if a convention cannot be applied. Instead, you have to implement a configuration within the infrastructure of your application by using a port adapter. The port adapter is connected to the specific driving adapter and performs the mapping to expose a port. For example, this is required for RESTfulHTTP. Typically, a specific driving adapter queries the configuration via annotations used in the port adapter.

Infrastructure strategies

Application infrastructure strategies provide how to map objects from the application core to a specific technology stack. For example, if you use a database for persisting your data, the strategy decides the ORM mapping of your objects.

Jexxa provides implementation of typical application infrastructure patterns to simplify development of driven adapters. If such a strategy is suitable for your application, the implementation of a driven adapter is just a facade that maps the interface of the outbound port to the methods of the strategy.

Available strategies in Jexxa are based on the standard javax interfaces (e.g., JMS or JDBC) and can be configured via Properties. This allows you to adjust the configuration to your development process. For example, you can easily switch your database technology between in memory or JDBC, or the used URL.

4.4. Package Jexxa-Core

Table 5. Describes the components of package JexxaCore.

Components

General Description

Realization in Jexxa

Core

This component includes class JexxaMain which is the entry point in the main-method of your application to use Jexxa.

JexxaMain provides a fluent API to explicitly show the binding of technology stacks to your inbound ports. In addition, it provides a BoundedContext which allows to control your application in your environment.

Factory

Instantiates ports and adapters and manage their life cycle.

Jexxa supports implicit constructor injection which is described in Dependency Injection (DI).

Convention

Provide classes to validate the compliance with conventions of ports and adapters.

Jexxa provides a fast fail approach regarding conventions. The conventions are described in Dependency Injection (DI).

5. Inversion of Control (IoC)

5.1. 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 for the following reason:

  • Constructor injection ensures that the dependencies required for the object to function properly are available immediately after creating the object.

  • Fields assigned in the constructor can be final. Thus, the object can be immutable or at least protect the corresponding fields.

  • No special annotations or configuration files are required so that the application core remains completely decoupled from Jexxa.

Within Jexxa we use conventions described in JexxaConventions to explicitly limit the direction of dependencies as described in Figure 1. Compared to other frameworks, these limitations could be considered puristic. However, they provide good guard rails to clarify the single responsibility of your ports.

5.2. Scope

Jexxa provides some simple mechanisms to define and control the scopes of ports and adapters which are described in this section.

5.2.1. Visibility of Ports and Adapters

Jexxa assumes a default package structure so that it knows which parts belong to the application core and infrastructure. This is required to validate the separation of these two parts and to create them by Jexxa’s DI mechanism. The default package structure as well how to adjust them to your needs is described in reference documentation.

5.2.2. Threading

Currently, Jexxa ensures that only a single thread is active within the application core. This greatly simplifies the development of the application core itself. Furthermore, this approach should be sufficient due to the following reasons:

  • Multi-threading is typically essential within technology stacks and not within the application core itself.

  • When you start developing your application, you have typically only a limited number of users.

  • When your application becomes a huge success and must scale to a lot of users, you should scale it by running multiple instances of the application. Today’s container solutions offer a much better scaling and managing approach.

5.2.3. Allocation scope of Dependencies

The allocation scope defines how many instances of components are created by Jexxa. This is described in Table 6.

Table 6. Allocation scope for the components in a Jexxa application.

Components

Scope

Reason

Driving Adapter

Managed as singleton and reused when it is bind to different ports.

Simplifies managing technical resources like network ports or IP addresses.

In case you need a strict control on how objects are exposed, for example, to different IP addresses, you have to use a specific driving adapter combined with port-adapter.

Inbound Port

  1. Inbound ports created by Jexxa are managed as singleton and reused if they are connected to different driving adapters.

  2. Inbound ports created by the application can be bound to adapters. In this case, the application has full control of the number of instances.

  1. The singleton scope supports designing stateless ports which are in general recommended.

  2. This is only recommended if your application core cannot fulfill the conventions of Jexxa (see JexxaConventions).

Outbound Port

None

None

Driven Adapter

It Is managed as a singleton and reused when it is injected into different ports.

The singleton scope supports designing stateless outbound ports which are in general recommended.

Port Adapter

Since Jexxa version 4.0.0 is managed as singleton and reused when it is bind to different ports.

The initial assumption of fine-grained control of how a driving adapter should be exposed was not required until now. In addition, it should be treated with explicit different types. On the other hand, singleton management simplifies the usage of interceptors. Therefore, we introduce this change with version 4.0.0

5.3. Transactions

Jexxa does not support any two-phase commit protocol for distributed transaction as required when using multiple driven adapters in a single use case. Traditional enterprise frameworks for example often spawn (by default) a transaction between the used technology stacks. Main reason was that multiple centralized databases were involved in a single operation that were even accessed by different applications. Within a microservice, such an approach is an anti-pattern and should be avoided.

5.3.1. Transaction Outbox Pattern

Instead, a typical business operation reads and updates its own database and publishes at most a domain event. On a technical layer, you can then use the transactional outbox pattern to achieve consistency between storing changes and publishing events. For these application scenarios Jexxa provides the TransactionalOutboxSender.