Getting Started with jMolecules and the (Classical) Onion Architecture, with a Spring Boot project

Featured image for sharing metadata for article

When you're starting on a fresh project, choosing a means for structuring your codebase is a difficult choice, as there are quite a few alternatives, and each of them have trade-offs, and at the end of the day, we're still trying to deliver the same thing.

Kislay Verma's article "how to organize your code" touches on this, and the benefits we can have by building the right architecture.

I've trialled a few ways of structuring projects, but generally default to the plain stack. Sometimes I'll put the underlying logic into a -core library, but it's never quite as well defined as doing it in terms of a complete Domain layer.

At last week's jChampions, I learned about jMolecules, which provides a common language of how to programmatically, and conversationally discuss architecture, as well as some ArchUnit rules to enforce the architecture rules.

jMolecules supports three styles of architecture out-of-the-box - Layered Architecture, Onion Architecture and Domain-Driven Design.

I'd recommend a read of Herbert Graca's articles on Layered Architecture, Onion Architecture and Domain-Driven Design, as they're great reads.

I started off with giving the Layered Architecture a go with a couple of projects, and turned on the "strict" mode in jMolecules before finding that I really wasn't getting on with it. I was finding that there were too many levels of indirection, and it was quite complex to add new things without a cascade of effort. It turns out that this is the "Lasagna Architecture" anti-pattern (as noted by Herbert) and is a common reason folks don't use the "strict" model, or the Layered model at all.

Next, I moved onto the Onion Architecture, not least because I could talk about Onion Rings at work in a serious, work-related manner.

As well as giving a migration a go for the Federated API Model at work, I also wanted to do this with a smaller project of my own that I could blog about regardless.

Code snippets can be found in full in a sample project on GitLab.

Before

Let's say that we have the following directory structure, which follows the stack model that Kislay mentions:

Package structure (before)
src
  |-main
  |  |-java
  |  |  |-me.jvt.hacking
  |  |  |  |-Application.java
  |  |  |  |-controller
  |  |  |  |  |-ApiController.java
  |  |  |  |-model
  |  |  |  |  |-ApiResponseContainer.java
  |  |  |  |  |-Api.java
  |  |  |  |-service
  |  |  |  |  |-NoopApiService.java
  |  |  |  |  |-ApiService.java
  |-test
  |  |-java
  |  |  |-me.jvt.hacking
  |  |  |  |-controller
  |  |  |  |  |-ApiControllerTest.java
  |  |  |  |-integration
  |  |  |  |  |-ApiControllerIntegrationTest.java
  |  |  |  |  |-ApplicationIntegrationTest.java

Migrating

Adding our ArchUnit tests

Let's start off by creating a very small ArchUnit test that enforces the Onion architecture:

package me.jvt.hacking.architecture;

import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;
import org.jmolecules.archunit.JMoleculesArchitectureRules;

@AnalyzeClasses(packages = "me.jvt.hacking")
class ArchitectureLayeringTest {
  @ArchTest
  @SuppressWarnings("unused") // because I don't like IntelliJ warnings
  private final ArchRule onionArchitecture = JMoleculesArchitectureRules.ensureOnionClassical();
}

There are quite a few other ones I've added into the project to enforce the structure we want.

Considering how our code maps to Onion rings

Before we can start migrating, we should think about the packages we've got and how they'd map to the onion rings:

Java Package/ClassOnion Ring
me.jvt.hacking.controllerInfrastructure Ring
me.jvt.hacking.modelInfrastructure Ring and Domain Model Ring
me.jvt.hacking.serviceApplication Ring or Domain Service Ring

While doing this, we notice that the Api object is reused between the Infrastructure and Domain model. Although that can be OK, the risk is that in the future we want to return different metadata, or with different field names in our HTTP layer, but then have to amend the underlying Domain object, which doesn't make sense, so we should split that Api object into different classes for different needs.

The example's ApiService isn't ideal, because it doesn't really do anything for business-logic so far, but the idea is that it would perform other checks such as validation of the underlying Domain Model objects, and retrieving them which means it'd be part of the Domain Service Ring. Alternatively, it could just delegate to Domain Service Ring objects / Domain Model Ring objects, so we could move it into the Application Ring.

Creating packages

I'd recommend creating fresh packages, which clearly line up with our onion rings.

For the Application ring, we'll create src/main/java/me/jvt/hacking/application/package-info.java:

@ApplicationServiceRing
package me.jvt.hacking.application;

import org.jmolecules.architecture.onion.classical.ApplicationServiceRing;

For the Domain Model ring, we'll create src/main/java/me/jvt/hacking/domain/model/package-info.java:

@DomainModelRing
package me.jvt.hacking.domain.model;

import org.jmolecules.architecture.onion.classical.DomainModelRing;

For the Domain Service ring, we'll create src/main/java/me/jvt/hacking/domain/service/package-info.java:

@DomainServiceRing
package me.jvt.hacking.domain.service;

import org.jmolecules.architecture.onion.classical.DomainServiceRing;

And for the Infrastructure ring, we'll create src/main/java/me/jvt/hacking/infrastructure/package-info.java:

@InfrastructureRing
package me.jvt.hacking.infrastructure;

import org.jmolecules.architecture.onion.classical.InfrastructureRing;

Moving packages

Finally, we need to set up our rings.

Most straightforward is the Infrastructure ring, which includes anything that deals with external parties and requests, such as our HTTP layer.

We can move our controller (and associated tests) as well as any HTTP response objects into the infrastructure package.

We also need to move our Application, which is the class annotated with SpringBootApplication into the application tier, as it's related to the way that the application is managed, but isn't as regularly changing as the Infrastructure Ring. Because it's no longer at the package root, we need to make sure scanBasePackages is set, and that Spring integration tests use @ContextConfiguration to make sure that we set up the integration tests accordingly.

We currently have Spring annotations on the ApiService which - at least in this contrive example - is going to be put into DomainServiceRing. Because the Domain ring is meant to be independent of the codebase that it's part of - as it contains core business models, rules and logic, we shouldn't be injecting in information about whether we are using Spring for Dependency Injection in our application, and need to remove it. This requires we wire in the NoopApiService with an @Bean configuration.

We then get the following setup:

After

Package structure (after)
src
  |-main
  |  |-java
  |  |  |-me.jvt.hacking
  |  |  |  |-application
  |  |  |  |  |-Application.java
  |  |  |  |  |-package-info.java
  |  |  |  |  |-SpringConfiguration.java
  |  |  |  |-domain
  |  |  |  |  |-model
  |  |  |  |  |  |-package-info.java
  |  |  |  |  |  |-Api.java
  |  |  |  |  |-service
  |  |  |  |  |  |-package-info.java
  |  |  |  |  |  |-NoopApiService.java
  |  |  |  |  |  |-ApiService.java
  |  |  |  |-infrastructure
  |  |  |  |  |-package-info.java
  |  |  |  |  |-models
  |  |  |  |  |  |-ApiResponseContainer.java
  |  |  |  |  |  |-Api.java
  |  |  |  |  |-controller
  |  |  |  |  |  |-ApiController.java
  |-test
  |  |-java
  |  |  |-me.jvt.hacking
  |  |  |  |-application
  |  |  |  |  |-infrastructure
  |  |  |  |  |-ApplicationIntegrationTest.java
  |  |  |  |-architecture
  |  |  |  |  |-ArchitectureLayeringTest.java
  |  |  |  |-infrastructure
  |  |  |  |  |-controller
  |  |  |  |  |  |-ApiControllerIntegrationTest.java
  |  |  |  |  |  |-ApiControllerTest.java

Thoughts

It's a little bit of overhead to migrate, so it's better to start when we're on a fresh project, but I wouldn't say it's bad enough to avoid it if you're already part way down the architecture.

jMolecules makes it easier, and I've found that writing more ArchUnit tests helps further solidify our basis and understand what should and shouldn't be in each ring.

I'm also starting to use this at work, too, and am hoping it'll give us a bit more guardrails wise, and be more considered with the way we think about our software architecture.

Written by Jamie Tanna's profile image Jamie Tanna on , and last updated on .

Content for this article is shared under the terms of the Creative Commons Attribution Non Commercial Share Alike 4.0 International, and code is shared under the Apache License 2.0.

#blogumentation #java #architecture #spring-boot #onion-architecture #jmolecules #archunit.

This post was filed under articles.

Interactions with this post

Interactions with this post

Below you can find the interactions that this page has had using WebMention.

Have you written a response to this post? Let me know the URL:

Do you not have a website set up with WebMention capabilities? You can use Comment Parade.