Spring Modulith Structures Spring Boot 3 Applications with Modules and Events

 

https://www.jnnctechnologies.com/





VMware has introduced an experimental project, Spring Modulith, to better structure monolithic Spring Boot 3 applications through modules and events. The project introduces new classes and annotations but doesn't generate code. Its modules don't use the Java Platform Module System (JPMS), but instead map to plain Java packages. Modules have an API, but Spring Modulith encourages using Spring application events as the "primary means of interaction." These events can be automatically persisted to an event log. Spring Modulith also eases the testing of modules and events.

The upcoming Spring Boot 3 framework, due in November 2022, is the foundation of Spring Modulith. So it has a baseline of Spring Framework 6, Java 17, and Jakarta EE 9. Spring Modulith is the successor of the Moduliths (with a trailing "s") project. That project used Spring Boot 2.7 and is now retired, receiving only bug fixes until November 2023.

Spring Modulith introduces its module abstraction because Java packages are not hierarchical. That's why in this sample code below, the SomethingOrderInternal class from the example.order.internal package is visible to all other classes, not just the ones from the example.order package:

Example
└─  src/main/java
   ├─  example
   |  └─  Application.java
   ├─  example.inventory
   |  ├─  InventoryManagement.java
   |  └─  SomethingInventoryInternal.java
   ├─  example.order
   |  └─  OrderManagement.java
   └─  example.order.internal
      └─  SomethingOrderInternal.java

Now Spring Modulith can't make Java compilation fail for violation of its module access rules. It uses unit tests instead: ApplicationModules.of(Application.class).verify() fails for the example above if another module tries to access the module-internal class SomethingOrderInternal. Spring Modulith relies on the ArchUnit project for this capability.

Spring Modulith encourages using Spring Framework application events for inter-module communication. It enhances these events with an Event Publication Registry which guarantees delivery by persisting events. Even if the entire application crashes, or just a module receiving the event does, the registry still delivers the event. The registry supports different serialization formats and defaults to JSON. The out-of-the-box persistence methods are JPA, JDBC, and MongoDB.

Testing events is also improved: This example demonstrates how the new PublishedEvents abstraction helps filter received events to OrderCompleted with a particular ID:

@Test
void publishesOrderCompletion(PublishedEvents events) {
  var reference = new Order();
  orders.complete(reference);

  var matchingMapped = events
    .ofType(OrderCompleted.class)
    .matchingMapped
      (OrderCompleted::getOrderId, 
       reference.getId()::equals);

  assertThat(matchingMapped).hasSize(1);
}

Spring Modulith can automatically publish events like HourHasPassedDayHasPassed, and WeekHasPassed at the end of a particular duration (such as an hour, day, or week). These central Passage of Time events are a convenient alternative to duplicated Spring @Scheduled annotations with cron triggers in the modules.

Spring Modulith does not include a workflow, choreography, or orchestration component for coordinating events, as the Spring ecosystem offers plenty of choices there.

Spring Modulith uses the new observability support of Spring Framework 6 to automatically create Micrometer spans for module API durations and event processing. Spring Modulith can also document modules by creating two kinds of AsciiDoc files: C4 and UML component diagrams for the relationship between modules and a so-called Application Module Canvas for the content of a single module, such as Spring beans and events.

InfoQ spoke to Spring Modulith project lead Oliver Drotbohm, Spring Staff 2 engineer at VMware.



No comments

If you have any doubts,please let me know

'; (function() { var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); })();