Single Responsibility and Code Organization Best Practices in Web Applications

I guess if you are reading this, you are working with web applications or web services. There are a number of posts on the internet that explain what shouldn’t be in a controller or in a model, but I didn’t find any proper summary of what should be in the MVC. So here is this blog post to collect our concepts.

Single responsibility & code organization in Web Apps title

MVC – View

If you write an MVC application, you will need views to show stuff to the users. The best idea I saw before is to separate your application and its UI completely. It will separate the work, separate the tests, separate the “what I need to show” from the “how I need to show it”. (It’s just a bonus if you need to write native apps to the site later, the REST functions will be available already.)

The most common problems with views arise when someone starts to add application logic to them. Languages and frameworks like PHP or Rails have made this really easy. Other template engines / frameworks, like JPA or ASP, make it a bit harder, but you can easily hack yourself in, and if you can reach the db from a view, what is holding you back from reading it? I saw a lot of code where the actual view called functions on an object; CPU or database heavy functions can be called too. It’s bad practice, a typical quick and dirty solution. Don’t do it!

So if you are not separating your views from your app completely, what is their responsibility? Views needs to show the data to the user. It should not generate data, should not convert data, it should read properties and write them out in a fine format.

 

MVC – Controller

This question is a bit trickier. What is the responsibility of a controller? How much logic is enough in a controller?

Most of the blogs and SO answers agree that a controller must contain as little business logic as possible. In the controllers, your main task is to

  • validate the request,
  • convert the request body to inner objects (typically json deserialization and validation),
  • call the proper business logic function(s),
  • generate/assign objects to views, and
  • return with the expected status code + format (json/xml/html).

So you can write really small controllers. This functionality can be written in 10-15 lines of code in most frameworks I’ve seen.

 

MVC – Models

More and more complicated questions. What is the model? The database object? The business object? The data transfer (view or rest) object? I think this is the biggest question; this is where nobody clarifies things. The easiest approach is if you say; those three can go into one thing. Sometimes it’s doable, but if you think it’s doable and you realize you need to split it in at least two parts halfway through, your code will become a mess… Try to design your models before the actual implementation, and separate database and data transfer objects.

Another question; who is responsible for serializing your objects? The object can save itself like apple.save(), or will a DAO (data access object) do it for us, like AppleDAO.save(apple)? There are pros and cons in both approaches, the frameworks are trying to push the DAO thing for a good reason. ASP.NET uses DataSourceControls, JPA uses EntityManager, Spring uses Adapters, Slick uses DAOs. They are all built by the same philosophy; the model should not do database interactions, like save itself, findByName and so on, it should be done by another object let’s call it DAO.

Ok, but if the database model DBO (DataBase Object) and the view model is separate, who is responsible for converting the data between these two? The DBO? Not really, why should a database model know every viewmodel and transform? The viewmodel is a much better place for these conversions, but still not the best place, it’s something that fits better to the controller scope (generate output, convert data), left this question open for a later decision, but we could agree that this is not fit well to the model’s scope, to transform itself to something else.

So, we are going to a no logic model object way. A model needs only fields, constructors, and maybe getters and setters. (Yes, I’m avoiding the business models right now too, I will explain this later.)

 

Data Access Objects

After we are agreed that the models should not access the database, we need to work with DAOs too. I think this is one of the most self-explanatory object type. It’s working with model objects and databases. This is where you need to implement the count / list / findby / save / etc. functions. You will need one DAO for each of your DBOs. If you change your database engine you need to modify only these objects.

 

So where does the business logic fit?

The simple answer is none of the above. You will need a separate layer.

We call this ControllerService in our applications; not the best name, but it describes what it does. Our approach is that the functions in this,

  • must be separated, and as clean as possible,
  • must use application objects as an input and an output, too, so the top level functions can be called directly from the controller (1 business call / controller function),
  • (mostly these are containing the transform functions too because they need to transform the viewmodels to DAOs and back the DAOs to viewmodels)
  • but the serialization/deserialization is totally separate from the actual business logic.

This approach has another good point, our business functions are as separated as our controllers (user functions go to the UserCS, product functions go to the ProductCS).

As every idea does, the ControllerServices has some problems too. First of all, if you have model specific common computations where do they fit? They neither fit the DAO nor a specific controller service, you need to make one more ObjectHelper layer for this. Secondly if you write monster controllers, you will write monster ControllerServices, too. I worked with 1kLOC controllers, this approach would not help with those files. Sometimes you need to split your code, and if it can’t split vertically (all of the code belongs to a customer), you need to split it horizontally (keep the top level functions only, and separate the lower level functions to another layer, they will be grouped to smaller files automatically). If you are trying to achieve the clean code principles and your functions have single responsibility, they will be separable.

 

Some real-life examples

In this section I will show some real-life projects (grouped by frameworks) and the concepts they are using.

 

Spring Boot

Spring is trying to hide the body parsing and response generating part in the controllers. So basically, when you write a controller in spring, you can write a ControllerService with the terms presented above. If you render views, I think it’s still not perfect and another layer is appreciated.

https://github.com/Raysmond/SpringBlog is a simple blog application. You can check the admin/PostController – this is our business logic with some annotations (like routing and params).

Another interesting thing in Spring, the DAO or as they call it, is the Repositories. You can check the PostRepository – it is the full DAO. You can write some custom searches, or let the compiler find out what you want to do. Your repositories will be clean and simple.

The models are a whole another thing. I don’t really like this kind of model using. They just convert the application model to DBO or json. You can annotate what are the computed values, and @Transient (not for the db) or non for user values and annotate them to @JsonIgnore. It can cause a big mess in a bigger model file though. For an example you can check this Post.

https://github.com/steve-perkins/fitnessjiffy-spring – This project separates the DTOs and the application objects, but it’s a fine example of how the placing of the converting functions are problematic.

 

ASP.NET

We don’t use ASP.NET, but if you want to be good, you must know your competition (smile)

https://github.com/VJAI/JustBlog – Again a simple blog application. The controller does the converting things, mostly like in Spring. Check out the BlogController .

The DAO is intresting again. The framework and all the examples are trying to show you how simply you can use a single repository file. If you check this BlogRepository, you will understand why the one model-one DAO is cleaner. It’s a 600LOC file of database operations. And we only have four objects (smile)

The models are similar to Spring’s approach, too. You can annotate the properties with validations and error messages, and you can use the same objects in the database and in the communication with the views, like in this Post.

 

Play Framework with Slick

We have no public projects yet, but I will write down the basics of our structure.

Controllers

We are trying to write as many minimal controller functions as possible, so our controllers are like:

All of our ControllerService functions return with an Either so we can extend the Try[Either[]] with a generateResponse method, and it can generate our response generally to all controllers:

 

ControllerServices

Yes, we simply generate an Either with the result. We are doing the json convert in there, too. At the moment, we haven’t had an application where the DTOs didn’t fit in this class as well. We are writing the business logic to this layer.

 

DAOs

It is a simple Slick DAO.

 

Models

The DBOs are like this:

And the DTOs are like this:

 

Conclusion

After all the things I described above, I think the most important thing is to structure your code, and don’t let yourself break this structure. Keep the things where you think they belong, and try to achieve a clean and readable concept. If you are organizing your code and laying the responsibilities down for each class, your work will be reusable and easy to extend later. If you are working in a team, it’s worth a couple of hours to agree on which layers will do what and why.

Gergő Törcsvári

Gergő Törcsvári

Software Developer at Wanari
I would love to change the world, but they won’t give me the source code (yet).

  • Tegi

    Thank you for the article. I think it touches on some important points, and these kinds of articles that cover such a subjective topic as code organization are really hard to write, and therefore they are hard to find.

    I used to employ this “ControllerService” model in all my projects. In my opinion they help to separate responsibilities, but they also tend to constrain the programmer’s thinking.

    A UserCS does things related to a user. So should it register the user, send activation email, handle user activation, export the user’s profile into PDF? You suggested that “monster services” should be separated. But what’s the criteria for the separation? Is registration and password reset OK, but when I add profile export is that too much?
    I think the service layer “pattern” is very similar to C-type programming with structs and methods. Objects are merely data stores, and services are collections of methods that operate on these data stores.
    In my opinion not only methods should have a single responsibility, but objects too. They should also encapsulate and hide their state and inner workings.
    This makes objects more testable, reusable, and even easier to understand. Complicated business cases should be structured by composing many of these simple objects, which use each other’s single purpose to achieve something more useful.

    • Gergő Törcsvári

      Thanks for the comment!

      What you are saying is totally true, but it depends on the size of your codebase. If I have only 10-15 business object I don’t really want to write a full interface segregation, but If I have 100 I just need to if I or anyone else want to handle the code.
      Most of the cases your db models will be so simple, you have 4-8 properties and only 1-2 functions. I think these objects should have as less dependency as possible, so they can be easily mockable. I think if your codebase is growing rapidly, you will want to separate your code to fully functional style and fully OO style objects. And as I said – “and if it can’t split vertically, you need to split it horizontally ” – sometimes you cant split your code this way, but this approach could give you a helping hand in the beginnings.
      A really good talk in this topic: https://www.destroyallsoftware.com/talks/boundaries