Chapter 9 – Get Your Hands Dirty on Clean Architecture

Chapter 9

Assembling the Application

Now that we have implemented some use cases, web adapters, and persistence adapters, we need to assemble them into a working application. As discussed in Chapter 3, Organizing Code, we rely on a dependency injection mechanism to instantiate our classes and wire them together at startup. In this chapter, we will discuss some approaches for how we can do this with plain Java and the Spring and Spring Boot frameworks.

Why Even Care about Assembly?

Why aren't we just instantiating the use cases and adapters when and where we need them? Because we want to keep the code dependencies pointing in the right direction. Remember: all dependencies should point inward, toward the domain code of our application, so that the domain code doesn't have to change when something in the outer layers changes.

If a use case needs to call a persistence adapter and just instantiates it itself, we have created a code dependency in the wrong direction. This is why we created outgoing port interfaces. The use case only knows an interface and is provided an implementation of this interface at runtime.

A nice side effect of this programming style is that the code we are creating is much better testable. If we can pass all the objects a class needs into its constructor, we can choose to pass in mocks instead of the real objects, which makes it easy to create an isolated unit test for the class.

So, who's responsible for creating our object instances? And how do we do it without violating the dependency rule?

The answer is that there must be a configuration component that is neutral to our architecture and that has a dependency to all classes in order to instantiate them, as shown in the following figure:

Figure 9.1: A neutral configuration component may access all classes in order to instantiate them

In the clean architecture introduced in Chapter 2, Inverting Dependencies, this configuration component would be in the outermost circle, which may access all inner layers, as defined by the dependency rule.

The configuration component is responsible for assembling a working application from the parts we provided. It must:

  • Create web adapter instances
  • Ensure that HTTP requests are actually routed to the web adapters
  • Create use case instances
  • Provide web adapters with use case instances
  • Create persistence adapter instances
  • Provide use cases with persistence adapter instances
  • Ensure that the persistence adapters can actually access the database

Besides that, the configuration component should be able to access certain sources of configuration parameters, such as configuration files or command-line parameters. During application assembly, the configuration component then passes these parameters on to the application components to control behavior, such as which database to access or which server to use for sending an email.

These are a lot of responsibilities (read: "reasons to change"). Aren't we violating the Single Responsibility Principle here? Yes, we are, but if we want to keep the rest of the application clean, we need an outside component that takes care of the wiring. And this component has to know all the moving parts to assemble them into a working application.

Assembling via Plain Code

There are several ways to implement a configuration component responsible for assembling the application. If we are building an application without the support of a dependency injection framework, we can create such a component with plain code:

package copyeditor.configuration;

class Application {

  public static void main(String[] args) {

    

    AccountRepository accountRepository = new AccountRepository();

    ActivityRepository activityRepository = new ActivityRepository();

    AccountPersistenceAdapter accountPersistenceAdapter =

        new AccountPersistenceAdapter(accountRepository, activityRepository);

    SendMoneyUseCase sendMoneyUseCase =

        new SendMoneyUseService(

            accountPersistenceAdapter,  // LoadAccountPort

            accountPersistenceAdapter); // UpdateAccountStatePort

    SendMoneyController sendMoneyController =

        new SendMoneyController(sendMoneyUseCase);

    

    startProcessingWebRequests(sendMoneyController);

    

  }

}

This code snippet is a simplified example of how such a configuration component might look. In Java, an application is started from the main method. Within this method, we instantiate all the classes we need, from the web controller to the persistence adapter, and wire them together.

Finally, we call the mystic method named startProcessingWebRequests(), which exposes the web controller via HTTP. This method is just a placeholder for any bootstrapping logic that is necessary to expose our web adapters via HTTP (we don't really want to implement this ourselves). The application is then ready to process requests.

This plain code approach is the most basic way of assembling an application. It has some drawbacks, however.

First of all, the preceding code is for an application that has only a single web controller, use case, and persistence adapter. Imagine how much code like this we would have to produce to bootstrap a full-blown enterprise application.

Second, since we are instantiating all classes ourselves from outside of their packages, those classes all need to be public. This means, for example, that Java doesn't prevent a use case directly accessing a persistence adapter since it's public. It would be nice if we could avoid unwanted dependencies like this by using package-private visibility.

Luckily, there are dependency injection frameworks that can do the dirty work for us while still maintaining package-private dependencies. The Spring Framework is currently the most popular one in the Java world. Spring also provides web and database support, among a lot of other things, so we don't have to implement the mystic startProcessingWebRequests() method after all.

Assembling via Spring's Classpath Scanning

If we use the Spring Framework to assemble our application, the result is called the application context. The application context contains all objects that together make up the application ("beans" in Java lingo).

Spring offers several approaches to assemble an application context, each having its own advantages and drawbacks. Let's start by discussing the most popular (and most convenient) approach: classpath scanning.

With classpath scanning, Spring goes through all the classes that are available in the classpath and searches for classes that are annotated with the @Component annotation. The framework then creates an object from each of these classes. The classes should have a constructor that takes all required fields as an argument, like our AccountPersistenceAdapter from Chapter 6, Implementing a Persistence Adapter:

@RequiredArgsConstructor

@Component

class AccountPersistenceAdapter implements

    LoadAccountPort,

    UpdateAccountStatePort {

  private final AccountRepository accountRepository;

  private final ActivityRepository activityRepository;

  private final AccountMapper accountMapper;

  @Override

  public Account loadAccount(

          AccountId accountId,

          LocalDateTime baselineDate) {

    // ...

  }

      

  @Override

  public void updateActivities(Account account) {

    // ...

  }

}

In this case, we didn't even write the constructor ourselves but instead let the Lombok library do it for us using the @RequiredArgsConstructor annotation, which creates a constructor that takes all final fields as arguments.

Spring will find this constructor and search for @Component-annotated classes of the required argument types and instantiate them in a similar manner to add them to the application context. Once all the required objects are available, it will finally call the constructor of AccountPersistenceAdapter and add the resulting object to the application context as well.

Classpath scanning is a very convenient way of assembling an application. We only have to sprinkle some @Component annotations across the code base and provide the right constructors.

We can also create our own stereotype annotation for Spring to pick up. We could, for example, create a @PersistenceAdapter annotation:

@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Component

public @interface PersistenceAdapter {

  @AliasFor(annotation = Component.class)

  String value() default "";

}

This annotation is meta-annotated with @Component to let Spring know that it should be picked up during classpath scanning. We could now use @PersistenceAdapter instead of @Component to mark our persistence adapter classes as parts of our application. With this annotation, we have made our architecture more evident to people reading the code.

The classpath scanning approach has its drawbacks, however. First, it's invasive in that it requires us to put a framework-specific annotation in our classes. If you are a clean architecture hardliner, you'd say that this is forbidden as it binds our code to a specific framework.

I'd say that in usual application development, a single annotation on a class is not such a big deal and can easily be refactored, if at all necessary.

In other contexts, however, such as when building a library or a framework for other developers to use, this might be a no-go, since we don't want to encumber our users with a dependency on the Spring Framework.

Another potential drawback of the classpath scanning approach is that magic things might happen. And by "magic" I mean the bad kind of magic, causing inexplicable effects that might take days to figure out if you are not a Spring expert.

Magic happens because classpath scanning is a very blunt weapon to use for application assembly. We simply point Spring at the parent package of our application and tell it to go looking for @Component-annotated classes within this package.

Do you know by heart every single class that exists within your application? Probably not. There's bound to be a class that we don't actually want to have in the application context. Perhaps this class even manipulates the application context in evil ways, causing errors that are hard to track.

Let's look at an alternative approach that gives us a little more control.

Assembling via Spring's Java Config

While classpath scanning is the cudgel of application assembly, Spring's Java Config is the scalpel. This approach is similar to the plain code approach introduced earlier in this chapter, but it's less messy and provides us with a framework so that we don't have to code everything by hand.

In this approach, we create configuration classes, each responsible for constructing a set of beans that are to be added to the application context.

For example, we could create a configuration class that is responsible for instantiating all of our persistence adapters:

@Configuration

@EnableJpaRepositories

class PersistenceAdapterConfiguration {

  

  @Bean

  AccountPersistenceAdapter accountPersistenceAdapter(

        AccountRepository accountRepository,

        ActivityRepository activityRepository,

        AccountMapper accountMapper){

    return new AccountPersistenceAdapter(

      accountRepository,

      activityRepository,

      accountMapper);

  }

  

  @Bean

  AccountMapper accountMapper(){

    return new AccountMapper();

  }

  

}

The @Configuration annotation marks this class as a configuration class to be picked up by Spring's classpath scanning. So, in this case, we are still using classpath scanning, but we only pick up our configuration classes instead of every single bean, which reduces the chance of evil magic happening.

The beans themselves are created within the @Bean-annotated factory methods of our configuration classes. In the preceding case, we add a persistence adapter to the application context. It needs two repositories and a mapper as input to its constructor. Spring automatically provides these objects as input to the factory methods.

But where does Spring get the repository objects from? If they are created manually in a factory method of another configuration class, then Spring would automatically provide them as parameters to the factory methods of the preceding code example. In this case, however, they are created by Spring itself, triggered by the @EnableJpaRepositories annotation. If Spring Boot finds this annotation, it will automatically provide implementations for all of the Spring Data repository interfaces we have defined.

If you are familiar with Spring Boot, you might know that we could have added the @EnableJpaRepositories annotation to the main application class instead of our custom configuration class. Yes, this is possible, but it would activate Java Persistence API (JPA) repositories every time the application is started up – even if we start the application within a test that doesn't actually need persistence. So, by moving such "feature annotations" to a separate configuration "module," we have just become much more flexible and can start up parts of our application instead of always having to start the whole thing.

With the PersistenceAdapterConfiguration class, we have created a tightly scoped persistence module that instantiates all the objects we need in our persistence layer. It will be automatically picked up by Spring's classpath scanning and we will still have full control over which beans are actually added to the application context.

Similarly, we could create configuration classes for web adapters, or for certain modules within our application layer. We could then create an application context that contains certain modules but mocks the beans of other modules, which gives us great flexibility in tests. We could even push the code of each of those modules into its own codebase, its own package, or its own Java Archive (JAR) file without much refactoring.

Also, this approach does not force us to sprinkle @Component annotations all over our codebase, as the classpath scanning approach does. So, we can keep our application layer clean without any dependency on the Spring Framework (or any other framework, for that matter).

There is a catch with this solution, however. If the configuration class is not within the same package as the classes of the beans it creates (the persistence adapter classes in this case), those classes must be public. To restrict visibility, we can use packages as module boundaries and create a dedicated configuration class within each package. This way, we cannot use sub-packages, though, as will be discussed in Chapter 10, Enforcing Architecture Boundaries.

How Does This Help Me Build Maintainable Software?

Spring and Spring Boot (and similar frameworks) provide a lot of features that make our lives easier. One of the main features is assembling applications out of the parts (classes) that we, as application developers, provide.

Classpath scanning is a very convenient feature. We only have to point Spring to a package and it assembles an application from the classes it finds. This allows for rapid development, with us not having to think about the application as a whole.

Once the code base grows, however, this quickly leads to a lack of transparency. We don't know which beans exactly are loaded into the application context. Also, we cannot easily startup isolated parts of the application context to use in tests.

By creating a dedicated configuration component responsible for assembling our application, we can liberate our application code from this responsibility (read: "reason for change" – remember the "S" in "SOLID"?). We are rewarded with highly cohesive modules that we can start up in isolation from each other and that we can easily move around within our codebase. As usual, this comes at the price of spending some extra time on maintaining this configuration component.