post-photo

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.

text

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

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,

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 :)

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 :)

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:

def list = {
 deadbolt.pattern(PrivilegeEnum.NEWS_LIST) { implicit request =>
  Try(NewsCS.list) generateResponse
 }
}
 
def insert = {
 deadbolt.pattern(PrivilegeEnum.NEWS_INSERT)(BodyParsers.parse.json) { implicit request =>
  val newsJR: JsResult[NewsEditorData] = Json.fromJson[NewsEditorData](request.body)
  Try(NewsCS.insert(newsJR)) generateResponse
 }
}

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:

def createResponseFromTry(tried: Try[Either[StatusErrorVO, Any]]): Result = {
 tried match {
  case Success(e: Either[StatusErrorVO, Any]) =>
   e match {
    case Right(json: JsValue) => Ok(json)
    case Right(str: String) => Ok(str)
    case Right(_) => Ok("")
 
    case Left(StatusErrorVO(UNAUTHORIZED, _)) =>
     Logger.warn("Unauthorized user")
     Unauthorized("")
 
    ...
   }
  case Failure(otherException: Throwable) =>
   Logger.error("An error occured", otherException)
   InternalServerError("")
 
 }
}

ControllerServices

def list = {
 val news = NewsDAO.runAwait(NewsDAO.all()).map(
  n => NewsListData(
   id = n.id.get,
   title = n.title,
   dateFrom = n.dateFrom,
   dateTo = n.dateTo,
   status = n.status,
   createdAt = n.createdAt
  )
 )
 Right(Json.toJson(news))
}
 
def delete(id: Long) = {
 NewsDAO.runAwait(NewsDAO.findById(id)) match {
  case Some(n: News) if n.status == NewsStatusEnum.DELETED => Left(StatusErrorVO(FORBIDDEN, s"News delete was not successful with id : $id. News is DELETED"))
  case Some(n: News) if n.status != NewsStatusEnum.DELETED =>
   val deleted = n.copy(status = NewsStatusEnum.DELETED)
   NewsDAO.runAwait(NewsDAO.update(deleted))
   Right("")
  case None => Left(StatusErrorVO(NOT_FOUND, s"News delete was not successful with id : $id. News not found"))
 }
}

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

object NewsDAO extends DAOBase {
 
 import databaseSupport.jodaSupport._
 import databaseSupport.profile.api._
 
 private[dao] val NewsCollection = TableQuery[NewsTable]
 
 def all(containsDeleted: Boolean = false) = if(containsDeleted) NewsCollection.result else NewsCollection.filter(_.status =!= NewsStatusEnum.DELETED).result
 
 def count(containsDeleted: Boolean = false) = if(containsDeleted) NewsCollection.length.result else NewsCollection.filter(_.status =!= NewsStatusEnum.DELETED).length.result
 
 def insert(news: News) = NewsCollection += news
 
 def insert(news: Seq[News]) = NewsCollection ++= news
 
 def update(news: News) = NewsCollection.filter(_.id === news.id).update(news)
 
 def findById(id: Long) = NewsCollection.filter(_.id === id).result.headOption
 
 def getActiveNews = {
  val now = DateTime.now
  NewsCollection
   .filter(_.status === NewsStatusEnum.VISIBLE)
   .filter(news => news.dateFrom < now || news.dateFrom.isEmpty)
   .filter(news => news.dateTo > now || news.dateTo.isEmpty)
   .result
 }
 
 private[dao] class NewsTable(tag: Tag) extends Table[News](tag, "NEWS") {
  def title: Rep[String] = column[String]("TITLE", O.Length(length = 255, varying = true))
 
  def subtitle: Rep[String] = column[String]("SUBTITLE", O.Length(length = 255, varying = true))
 
  def content: Rep[String] = column[String]("CONTENT", O.Length(length = 255, varying = true))
 
  def dateFrom: Rep[Option[DateTime]] = column[Option[DateTime]]("DATE_FROM")
 
  def dateTo: Rep[Option[DateTime]] = column[Option[DateTime]]("DATE_TO")
 
  def status: Rep[NewsStatusEnum] = column[NewsStatusEnum]("STATUS")
 
  def bannerImageUrl: Rep[String] = column[String]("BANNER_IMG_URL", O.Length(length = 255, varying = true))
 
  def iconImageUrl: Rep[String] = column[String]("ICON_IMG_URL", O.Length(length = 255, varying = true))
 
  def createdAt: Rep[DateTime] = column[DateTime]("CREATED_AT")
 
  def id: Rep[Long] = column[Long]("ID", O.PrimaryKey, O.AutoInc)
 
  def * : ProvenShape[News] = (
   title,
   subtitle,
   content,
   dateFrom,
   dateTo,
   status,
   bannerImageUrl,
   iconImageUrl,
   createdAt,
   id.?
   ) <>(News.tupled, News.unapply)
 }
 
}

It is a simple Slick DAO.

Models

The DBOs are like this:

case class News(
         title: String,
         subtitle: String ,
         content: String,
         dateFrom: Option[DateTime] = None,
         dateTo: Option[DateTime] = None,
         status: NewsStatusEnum,
         bannerImageUrl: String,
         iconImageUrl: String,
         createdAt: DateTime,
         id: Option[Long] = None
         ){
 val bannerImageName =  bannerImageUrl.split("/").last
 val iconImageName =  bannerImageUrl.split("/").last
}

And the DTOs are like this:

case class NewsListData(
             id: Long,
             title: String,
             dateFrom: Option[DateTime],
             dateTo: Option[DateTime],
             status: NewsStatusEnum,
             createdAt: DateTime
             )
 
case class NewsEditorData(
              title: String,
              subtitle: String,
              content: String,
              dateFrom: Option[DateTime],
              dateTo: Option[DateTime],
              bannerImageUrl: String,
              iconImageUrl: String
              )

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.

member photo

His precision is only coupled by his attention to detail. (Really.) He is passionate about becoming more and more effective in software development and loves experimenting with new technologies.

Latest post by Gergő Törcsvári

Learning by Doing – BlockChain & Akka Tutorial