11.11.  How to Organize your Code

In this article I am going to present a realistic example that will show you how to organize your code and how to describe this organization using our architecture DSL. Let us assume we are building a micro-service that manages customers, products and orders. A high level architecture diagram would look like this:

Architecture of the order management micro service
Figure 11.2. Architecture of the order management micro service

It is always a good idea to cut your system along functionality, and here we can easily see three subsystems. In Java you would map those subsystems to packages, in other languages you might organize your subsystem into separate folders on your file system and use namespaces if they are available.

Let us assume the system is written in Java and its name is "Order Management". In that case we would organize the code into those 3 packages:

com.hello2morrow.ordermanagement.order
com.hello2morrow.ordermanagement.customer
com.hello2morrow.ordermanagement.product            
    

This can easily be mapped to our DSL:

artifact Order
{
    include "**/order/**"
    connect to Customer, Product
}
 
artifact Customer
{
    include "**/customer/**"
}
 
artifact Product
{
    include "**/product/**"
}
    

Internally all three subsystem have a couple of layers and the layering is usually the same for all subsystems. In our example we have four layers:

Layering of subsystems
Figure 11.3. Layering of a subsystem

A service would only expose its service ad its model layer to the outside. The service layer contains all the service interfaces and talks to the controller and the model layer. The controller layer contains all the business logic and uses the data access layer to retrieve or persist data using JDBC. The model layer is defining the entities we are working with.

We will use a separate architecture file named "layering.arc" to describe our layering:

// layering.arc
artifact Service
{
    include "**/service/**"
    connect to Controller
}
 
artifact Controller
{
    include "**/controller/**"
    connect to DataAccess
}
 
require "JDBC"
 
artifact DataAccess
{
    include "**/data/**"
    connect to JDBC
}
 
public artifact Model
{
    include "**/model/**"
}
 
interface IService
{
    export Service, Model
}
    

Please note, that we declared "Model" as a public artifact. That saves us the need to explicitly connect all the other layers to "Model". Also note the "require" statement. Here refer to a third architecture file, that contains the definition of the artifact JDBC. This way we can ensure that only the data access layer can make JDBC calls. using "require" will only declare the artifacts contained in the required file, but not define them. This means that the artifacts in "JDBC" have to be defined on another level. The interface is used to define the exposed parts of a subsystem. When connecting to the "IService" interface you have only access to the "Service" and the "Model" layer.

NOTE

Architecture files using "require" are not self-contained and cannot be added to the architecture check!

Now we use apply statements to apply the layering to our three subsystems:

artifact Order
{
    include "**/order/**"
    apply "layering"
    // Connect to the IService interface of Customer and Product
    connect to Customer.IService, Product.IService
}
 
artifact Customer
{
    include "**/customer/**"
    apply "layering"
}
 
artifact Product
{
    include "**/product/**"
    apply "layering"
}
 
// By using apply we define the artifacts of "JDBC" in this scope
apply "JDBC"
    

We also apply "JDBC" in the outermost scope to ensure that the artifacts in there are defined exactly once.

For the sake of completeness, here is the definition of "JDBC.arc":

// JDBC.arc
artifact JDBC
{
    include "**/javax/sql/**"
}
    

By using smart package naming it becomes easy to map your code to the architecture description. For example the order subsystem would have four packages:

com.hello2morrow.ordermanagement.order.service
com.hello2morrow.ordermanagement.order.controller
com.hello2morrow.ordermanagement.order.data
com.hello2morrow.ordermanagement.order.model
    

As you can see it required relatively little effort to create a formal and enforceable architecture description for our example.