-
Notifications
You must be signed in to change notification settings - Fork 4
Architecture
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 representation of the binding between ports and adapters in the main method so that you can exchange technology stacks independently.
-
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 following paradigms:
-
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.
-
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. Finally, prefer a convention over configuration approach to standardize the integration of technology stacks and to reduce the complexity of their configuration.
-
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.
Even if you use real powerful frameworks for good reason within your application I hope that you find Jexxa helpful in some way.
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 the J2EE standard provide powerful features and greatly support development of complex business applications. On the other side, these frameworks can unintentionally be misused by developers 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. 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 difficult 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 difficult or even impossible, so a disruptive technology will cause your application core to become legacy as well. A common example are 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 takes 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.
|
❗
|
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 a domain specific software in order 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 business 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 should be as simple as possible and not an
egg-laying wool-milk sow.
|
❗
|
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.
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. This is especially true for the entry points of your application.
This might be obvious to incoming synchronous calls (RMI), but can be hard to see for incoming asynchronous messaging.
|
❗
|
Jexxa uses explicit binding for driving adapters so that the main method represents the starting point of the flow of control. |
One of the key aspects for durable software systems is the ability to use arbitrary technology stacks which do not exist at the point in time the application was developed.
|
❗
|
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 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 supports security aspects so that admin functionality is just offered via JMX.
Like any other framework, Jexxa takes control about 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.
Based on my experience, I can only recommend annotations within the application core for the following reasons:
-
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.
Driven adapters belong to the infrastructure of an application. Thus, their implementation should be as simple as possible so that they can be implemented by junior developers.
|
❗
|
Jexxa provides driven adapter 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 seems to be so obvious, so we directly agree. Therefore, I would like to explain the most important advantages of using the strategy pattern especially for the training of new 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
Aggregatesinclude 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.
Jexxa has a strong focus on Domain Driven Design and uses a lot of terminology 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 provides the required technology stacks.
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. |
<<Jexxa>> and light grey background are provided by Jexxa. Packages labeled with <<Application>> have to be implemented.@startuml
skinparam PackagePadding 20
skinparam linetype ortho
package JexxaCore <<Jexxa>> #DDDDDD {
[Convention] <<Inversion of Control>>
[Core] <<Jexxa API>>
[Factory] <<Inversion of Control>>
}
package JexxaInfrastructure <<Jexxa>> #DDDDDD {
[Generic Driving Adapters] <<Driving Adapter>>
[Specific Driving Adapters] <<Driving Adapter>>
[Driven Adapter Strategies] <<Driven Adapter Strategy>>
}
package ApplicationCore <<Application>> {
[Inbound Ports] <<Port>>
[Outbound Ports] <<Port>>
}
package Infrastructure <<Application>> {
[Port Adapters] <<Driving Adapter>>
[Driven Adapters] <<Driven Adapter>>
}
[Specific Driving Adapters] o-right-> [Port Adapters]
[Generic Driving Adapters] o-right-> [Inbound Ports]
[Port Adapters] *-right-> [Inbound Ports]
[Inbound Ports] o-down-> [Outbound Ports]
[Outbound Ports] <|.. [Driven Adapters]
[Driven Adapters] o-left-[Driven Adapter Strategies]
[Factory] ..> Infrastructure : create
[Factory] ..> ApplicationCore : create
[Factory] ..> JexxaInfrastructure : create
[Generic Driving Adapters] -[hidden]- [Port Adapters]
[Generic Driving Adapters] -[hidden]- [Specific Driving Adapters]
[Specific Driving Adapters] -[hidden]- [Driven Adapter Strategies]
[Port Adapters] -[hidden]- [Driven Adapters]
[Core] -left-> [Factory]
[Core] -up-> [Convention]
@enduml
Table 1 describes the packages of an application based on Jexxa.
Package |
Description |
|
This package includes your technology-agnostic business application. |
|
This package includes the glue code to bind your technology-agnostic business application to the package |
|
This package includes the provided driving adapter of Jexxa as well as the driven adapter strategies which simplify the application specific driven adapter |
|
This package includes the core of Jexxa and manages the lifecycle of both ports and adapters. The details are described in Section Inversion of Control (IoC). The functionality of this package is used via a fluent API within the main method of your application. |
The components of package ApplicationCore are:
Components |
General Description |
Support by Jexxa |
|
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. |
|
Outbound Ports |
Outbound ports belong to the application core but only as interface. These interfaces are implemented in package |
Outbound ports are 'just' interfaces that must be defined by your application core. Jexxa provides support to implement these interfaces by so-called driven adapter strategies. |
The components of package Infrastructure are:
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 operates on these interfaces. Typically, they map domain objects to a specific technology stack. |
Jexxa provides driven adapter strategies 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. |
The components of JexxaInfrastructure are:
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 in form of 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. |
Driven Adapter Strategies |
Driven adapter 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 some driven adapter strategies 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 which 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 |
Components |
General Description |
Realization in Jexxa |
Core |
This component includes class |
|
Factory |
Instantiates ports and adapters and manages 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). |
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 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 Table 6 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.
Components |
Conventions |
Reason |
||
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
|
||
Inbound Port |
|
|
||
Outbound Port |
None |
None |
||
Driven Adapter |
|
|
||
Port Adapter |
|
|
|
ℹ️
|
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.
|
Jexxa provides some simple mechanisms to define and control the scopes of ports and adapters which are described in this section.
By default, you have to tell Jexxa the location of your driven adapter and ports on a package level so that they can be created by Jexxa’s DI mechanism. This allows an application to specify used ports and adapters on a very fine-grained level.
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 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.
The allocation scope defines how many instances of components are created by Jexxa. This is described in Table 7.
Components |
Scope |
Reason |
||
Driving Adapter |
Is managed as singleton and reused when it is bind to different ports. |
Simplifies managing technical resources like network ports or IP addresses.
|
||
Inbound Port |
|
|
||
Outbound Port |
None |
None |
||
Driven Adapter |
Is managed as singleton and reused when it is injected into different ports. |
The singleton scope supports designing stateless outbound ports which is in general recommended. |
||
Port Adapter |
Is always created new and not reused. |
Allows fine-grained control of how a driving adapter should expose the included port. For example, you can define that specific ports are only available via a management IP address. |
Jexxa does not support any transaction 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, such as a database and a messaging system, to ensure that data is only written into the database when publishing it to a messaging bus is successful. This is a great feature if you really need it. Unfortunately, it is also often used to compensate programming errors or even a bad software design. For example, I saw it quite often to handle 'optimistic lock exception' by using retry and transaction mechanisms of a framework.
Instead, we recommend building your ports of the application core so that they provide an idempotent semantic.