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(HTTPGET
), as well as sending data down for storing (HTTPPOST
). 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 aviewmodel
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 (ModelMap
s), 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>Model
s 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.