Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Maintaining Clean Architecture in Spring MVC with a data-centric approach

Maintaining Clean Architecture in Spring MVC with a data-centric approach

I'm trying to map out the architecture for the front-end of a new Java-based web app (portal type application) we are making at work. I want to get this right from day one, and I would like to kick off a discussion here to help me implement Uncle Bob's Clean Architecture in my architectural design.

Here's a quick run-down of our tech stack, top to bottom (the technology isn't importance, the structure is):

  • Oracle Database
  • Oracle Service Bus exposing services using WSDLs
  • JAX-WS generated Java-classes from the WSDLs (let's call this the "generated service layer")
  • A Domain module consisting of POJOs mapped to the generated data objects
  • A Consumer-module exposing the "generated service layer" to the front-end application
  • A Spring MVC based front-end module using FreeMarker to render the views

A key point:

In particular, the name of something declared in an outer circle must not be mentioned by the code in the an inner circle. That includes, functions, classes. variables, or any other named software entity.

Attempting to adhere to Bob's Clean Architecture, I've gone back and forth a bit with myself regarding where to place the application logic, namely the "Use Case"-layer in his architecture.

Here is the approach I've come up with:

Layer 1 - Entities

Entities encapsulate Enterprise wide business rules.

This is where our Domain module containing the domain-objects lives, these are self-containing objects with minimal dependencies on each other. Only logic pertaining to the objects themselves may live on these domain objects, and no use-case specific logic.

Access to our database is exposed via WSDLs using a service bus that transforms the data, as opposed to an ORM like JPA or Hibernate. Because of this, we do not have "entities" in the traditional sense (with Ids), but a data-centric approach making this layer a data access layer, presented to the rest of the application by the Consumer-module.

Layer 2 - Use Cases

The software in this layer contains application specific business rules.

This is where logic specific to our application's use cases lives. Changes to this layer should not affect the data access layer (layer 1). Changes to the GUI or framework implementation (Spring MVC) should not affect this layer.

This is where it gets a little tricky: Since our data access layer (in layer 1) must be kept clean of application logic, we need a layer that facilitates use of that layer in a fashion that suits the use cases. One solution I've found to this problem is the use a variant of the "MVVM-pattern" that I choose to call MVC-VM. See below for an explanation. The "VM"-part of this lives in this Use Case-layer, represented by *ViewModel-classes that encapsulate this Use Case-specific logic.

Layer 3 - Interface Adapters

The software in this layer is a set of adapters that convert data from the format most convenient for the use cases and entities, to the format most convenient for some external agency such as the Database or the Web.

This is where the MVC-architecture of our GUI lives (the "MVC" in our "MVC-VM"). Essentially this is when the Controller-classes get data from the *ViewModel-classes and puts it in Spring MVC's ModelMap ojects that are used directly by the FreeMarker-templates in the View.

The way I see it, the servicebus would in our case also fall in under this layer.

Layer 4 - Frameworks and Drivers

Generally you don’t write much code in this layer other than glue code that communicates to the next circle inwards.

This layer is really just a configuration-layer in our application, namely the Spring configuration. This would for example be where we specify that FreeMarker is used to render the view.


Model View ViewModel Pattern

MVVM facilitates a clear separation of the development of the graphical user interface (either as markup language or GUI code) from the development of the business logic or back end logic known as the model (also known as the data model to distinguish it from the view model). The view model of MVVM is a value converter meaning that the view model is responsible for exposing the data objects from the model in such a way that those objects are easily managed and consumed.

More on the MVVM-pattern at Wikipedia.

The MVC-VM roles would be fulfilled in our application like so:

  • Model - represented simply by the ModelMap datastructure in Spring MVC that is used by the view templates.
  • View - FreeMarker templates
  • Controller Spring's Controller-classes that directs HTTP URL requests to specific handlers (and as such functions as a FrontController). The handlers in these classes are responsible for fetching data from the use case-layer and pushing it out to the view templates when showing data(HTTP GET), as well as sending data down for storing (HTTP POST). This way it essentially functions as a binder between the ViewModel and View, using the Model.

  • ViewModel - These classes are responsible for 1) structuring data from the data access layer in a fashion that is usable by the View and 2) treat data-input from the View. "Treat" means to validate and to break down the data so that it can be sent down the stack for storing. This layer would take form as <UseCase>VM-classes in a viewmodel package in our Spring MVC front-end module.

A key component here is the implicit binding that happens in Spring MVC between ModelMap and the FreeMarker-templates. The templates only use the model (ModelMaps), where the controller has put the data in a format it can use. That way we can make templates like so:

<body>
  <h1>Welcome ${user}!</h1>
  <p>Our latest product:
  <a href="${latestProduct.url}">${latestProduct.name}</a>!
</body>

I apologize for the verbose explanation, but I could not explain this (relatively simple) architecture in fewer words.

I would greatly appreciate some input on my approach here - am I on the right track? Does the MVC-VM thing make sense? Am I violating any Clean Architecture Principles?

There are of course many solutions to this, but I am trying to find a solution that is 1) not over-engineered and 2) adheres to the principles of Bob's Clean Architecture.


Update:

I think the key issue that puts me off here is what form the "Use case" layer takes in this application. Remember we have an MVC front-end that gets data from a data access layer. If the MVC part fits in Bob's "Interfaces adapters", and the domain models of the data layer fit in Bob's "Entities" layer, then what do I call the use case classes that implement application logic? I am tempted to just call them <UseCase>Models and put them in the MVC project, but according to Bob

The models are likely just data structures that are passed from the controllers to the use cases, and then back from the use cases to the presenters and views.

so that means my model objects should be "dumb" (like a simple Map. ModelMap in Spring) and it is then the responsibility of the controller to put data from the Use Case class into this Map-structure.

So again, what form does my Use Case-classes take? How about <UseCase>Interactor?

But in conclusion I realize that the MVC-MV-thing is over-engineering (or simply incorrect) - as "mikalai" indicates below this essentially just a two-layer applcation in its current form; a data access layer and a front-end MVC layer. Simple as that.

like image 886
Aksel Gresvig Avatar asked Oct 14 '12 15:10

Aksel Gresvig


People also ask

What is clean architecture in spring boot?

The idea of Clean Architecture is to put delivery and gateway at the edges of our design. Business logic should not depend on whether we expose a REST or a GraphQL API, and it should not depend on where we get data from — a database, a microservice API exposed via gRPC or REST, or just a simple CSV file.

Why is it important to build an app with clean architecture?

This makes the application easy to maintain, extensible, and testable because working with one module won't impact the other modules unintentionally. A well-built clean architecture usually consists of at least four high-level layers, which Martin defines as: Entities: Enterprise-wide business rules.

What is clean architecture in Java?

Clean architecture is a staple of the modern app development space. Particularly popular for Java and Android developers, this architecture is designed to make it easier to create stable apps even when outer elements such as UI, databases, or external APIs are always changing.


2 Answers

Whoa that was a lot. And I think you have mostly translated Uncle Bob's jargon over to your Spring Java app.

Since architecture is mostly opinion and since your question is sort of asking for one...

There are many different styles of architecture and ... most are overrated. Because most are the same thing: higher cohesion and looser coupling through indirection and abstraction.

What matters MOST (IMHO) are the dependencies. Making lots of small projects as opposed to one giant monolithic project is the best way to get "clean" architecture.

Your most important technology for clean architecture will not be "Spring MVC" technology or "Freemarker" templating language, or another Dr. Dobb's article with diagrams of boxes, hexagons and various other abstract polygons.

Focus on your build and dependency management technology. It is because this technology will enforce your architecture rules.

Also if your code is hard to test.. you probably have bad architecture.

Focus on making your code easy to test and write lots of tests.

If you do that it will be easy to change your code with out worry ... so much you could even change your architecture :)

Beware of focusing too much an bull#%$@# architecture rules. Seriously: if your code is easy to test, easy to change, easy to understand and performs wells then you have a good architecture. There is no 6 weeks to 6 pack abs article to do this (sorry Uncle Bob). It takes experience and time... there is no magic bullet plan.

So here my own "clean" architecture... I mean guidelines:

  • Make many small projects
  • Use dependency management (ie Maven, Gradle)
  • Refactor constantly
  • Understand and use some sort of dependency injection (Spring)
  • Write unit tests
  • Understand cross cutting concerns (ie when you need AspectJ, metaprogramming, etc..)
like image 57
Adam Gent Avatar answered Sep 21 '22 21:09

Adam Gent


My solution

So it turns out that implementing Bob's "Clean Architecture" in Java/Spring MVC is borderline non-trivial and requires more architectural facets than I originally had included.
And I could actually not find any example of implementations online.

Evidently my architecture was missing a separate module for the "Use Case"-layer as this logic should not live in the Spring MVC Web module (and not be called "*ViewModel"). The Web/MVC module is simply a detail of the application, and the application logic should be completely separated from it, and separately testable.

This new "Use Case"-module now contains *Interactor-classes which get data from the domain module (entities). Moreover, "Request/Response Objects" are needed to facilitate the communication between the MVC/Web-module and the Use Case module.

My dependency chain now looks like this:

Spring MVC module -> Use Case module -> Domain module

where every arrow (dependency) takes form as a Boundary, meaning an interface is defined in the module to the right of the arrow that is implemented where required and injected where needed (Inversion of Control).

Here are the interfaces I ended up with (per Use Case):

I<UseCase>Request - implemented in the MVC module, instantiated in the Controller I<UseCase>Response - implemented in the Use Case module, instantiated in the Interactor I<UseCase>Interactor - implemented in the UseCase module, injected in the Controller I<UseCase>Consumer - implemented in the Domain module, injected in the Interactor

How it works?
The Controller takes parameters from the HTTP request and packs it in a RequestModel which it passes down to the Interactor. The Interactor fetches the data it needs from the domain module *Consumer and imposes it's application specific logic on it, then puts it in a ResponseModel and sends it back up to the Controller. The Controller then finally simply puts this (now GUI-friendly) simple data in a Map object and forwards it to the FreeMarker template which then uses this data directly and renders the HTML.

A Presenter could get involved in that last part there, making this an implementation of the Model-View-Presenter pattern, but I'm leaving that for now.

My conclusion

I ended up with more files than what is necessary this early in development, strictly speaking. However as the complexity and size of the application grows, I am confident that this structure keeps it easy for us to maintain low coupling and high cohesion. Also, the Web-module is now easily replaceable - it just delivers requests to the use-case module and receives response-objects. Moreover, each layer of the application (domain logic, application logic, and GUI logic) is separately testable, with only the View-part requiring a webserver in order to be tested.

Thanks for all advice and pointers I received here. And please comment on my solution - I don't claim that it is perfect.

like image 20
Aksel Gresvig Avatar answered Sep 19 '22 21:09

Aksel Gresvig