Building modular apps using Spring

When looking at most of the sample applications and code examples for Spring framework it may seem that everything should be defined as a bean. However, if we want to achieve nicely modular design then we should try to do the exact opposite - have each module expose as little as possible as a bean.

In essence beans are just like global variables. We cannot do anything to control or restrict who can access them. There are no namespaces that might give us clues if what we are accessing is something quite close to us or something we should not have any dependency on. By exposing every Service as a bean we effectively make it part of the API of given module. The larger the API the harder it is to make sure that different parts of our system stay loosely coupled.

If we have small application then perhaps this is not so big issue but the bigger our codebase gets the more dangerous this jungle of beans will become. We may have very nice and thought through package structure but by exposing all behavior in one single namespace we nullify all that effort.

Misreading framework examples

The problem with code examples is not anything specific to Spring. In fact I think Spring has awesome documentation which probably has no small role in its success. It's just that if we want to show some specific feature of framework X then it is quite obvious that we want to keep the code as simple and unopinionated as possible. Our goal is to show that framework can be used together with any kind of application design.

However, the problem is that for developers reading this documentation it is very easy to start taking these code examples as "the way how systems should be built". No matter how unopinionated and impersonal style was used in examples it will still inevitably suggest some kind of approach to system design. Of course defining beans is just one aspect that can be misread. There are many other similar simplifications like implementing a Service for each Entity or having package structure per stereotype and not per module. Both are good enough for sample application but inevitably fall short for any bigger production application.

Only expose API beans

So instead of making everything a bean we should only expose Services that form the API of given module.

These API beans are both Services that are being accessed by other modules inside our applications but also things that implement the public API of our application. If we think in terms of hexagonal architecture then all Adapters can be defined as Spring beans.

Spring supports this kind of design thanks to its Java config.

For example:

@Bean
ProfileGateway profileGateway() {
  return new ProfileGatewayWithFallback(
    new ClientBasedProfileGateway(
      new ProfilesFromProfileService(profileClient),
        new CachedProfiles(repository)
      ),
      errorReporter
  );
}

Here we define just one bean that wraps access to some external profile-service instead of having 4 beans. We only want profile-service to be accessed via profileGateway bean and we do not want these other 3 Services to be used anywhere outside of the profile module.

Define one configuration class per each module

Another aspect that I have often found myself at loss is deciding how to split different bean configurations. Obviously we could have everything in one configuration class but it grows quite quickly even for relatively small microservice.

Again thinking in terms of modules helps here. It makes sense to have separate @Configuration for each module. This also given additional benefit when running integration tests as we have logical subsets of our application that we can start.

Testability

If we look at things from testability perspective then it may seem that having everything as a bean gives us more flexibility to choose what parts of application we want to have wired together by Spring and what things to replace with mocks. One might argue that now that we have less beans we will be more constrained in what we can stub/mock. Yes that is true but it is not necessarily a bad thing.

I have often seen integration tests that launch dozens of beans and then seemingly randomly mock out just a few. The problem with such tests is that they are this strange amalgam between unit and integration tests. I find that very often these tests are better rewritten as plain unit tests without any framework dependency or as high level component tests that test the microservice as a whole.

Writing in-process component tests is very suitable for doing high level acceptance testing that complements unit tests as suggested in Growing Object-Oriented Software, Guided by Tests book.

For such component tests we can do all input and output through HTTP API (using Spring MVC test framework) and stub only Adapter beans to external systems. I have rarely seen need for stubbing things out on a more granular level.

Following is an example of placing an order for transferring 2000 EUR to GBP in a cross-currency money transfer application. Test is written using Groovy and Spock.

def "places new order and updates it"() {
  given:
    // we set up some stubs for external services
    rateGateway.rate 1.234
    pricingGateway.fee 5.5

  when:
    // this is a wrapper around Spring MockMvc 
    // using Groovy JsonBuilder to put together the request
    def order = quoteService.placeOrder({
      profile 111
      sourceCurrency "EUR"
      targetCurrency "GBP"
      sourceAmount 1000
    })

    quoteService.updateOrder(
      order.id, 
      {
        sourceAmount 2000
      }
    )

    def fetchedOrder = quoteService.getById(order.id)

  then:
    fetchedOrder.id == order.id
    fetchedOrder.profile == 111
    fetchedOrder.sourceAmount == 2000
    fetchedOrder.rate == 1.234    
    fetchedOrder.fee == 5.5 
    fetchedOrder.sourceCurrency == "EUR"
    fetchedOrder.targetCurrency == "GBP"   
}

* Used image of a spring without framework from pxhere