Good code is like an onion, it has layers

Good code is like an onion, it has layers

TheWizKid's photo
TheWizKid
·Oct 11, 2022·

7 min read

Table of contents

  • Teaching some new recruits
  • The code that forced me to look and understand layers
  • Practice what I preach

The stack — Java, GWT, Mysql

The Experience — 11 Years

The Job — Senior Developer at Startup

Teaching some new recruits

It was my 3rd year at the startup, things were going well, the company was growing. We needed more working hands, so like any company, we hired a new group of devs, straight off the assembly line (because why spread it out, or get someone with experience). I don't exactly remember how it came to be, but I was in charge of getting them up to speed. This was my first true taste of teaching and leading.

I was always good at winging explanations, and presentations, so it was no different here. I showed them the ropes, the way things were put together, and what talked to what and how.

The problem came when I needed to explain the way the code was written, and why it was written that way. We were using GWT, and for those who don't know, it allowed us to write the client code as JAVA and use the same entities as we do with the DB.

To say that it was useful was an understatement, but like everything in life, this had its drawbacks. Namely everything you save in the DB is sent to the client. This could and did cause many performance issues, and looking back I'm pretty sure there were security issues stemming from this behavior. This code was not layered correctly, but I didn't quite know it yet.

The code that forced me to look and understand layers

It was around this time that my friend started working on another startup, there he worked with this outsourced dev. She had written the main structure of the server code, and had very distinct layers and separation.

I try not to go too deep in technical terms in these posts, but I kind of need to here. There are a few different actors in our story, and each has some specific needs and talents.

The DB

The job of the DB is simple, save the objects you need to, and allow you to query them when needed. So naturally, this is where you need to think of how to save things, how they are connected to the other entities in the system, and how you are going to query them.

The Server

Its main job is to process things, take requests from the client, do some logic, and save it to the DB. This is the man in the middle, between the client and the DB.

The Client

This is where things are displayed, not everything you need to do the job needs to be displayed. Sometimes you want to display things as a mix of other things.

Now I want to focus on the server, this is where my expertise is, and this is where the layers are most prevalent. It's the server's job to get tasks from the client and process them, update the DB, and send the results back for display. Let's break this down into meaningful layers.

[API] ← RO (Representation Object)→ [Service] ← Entity/RO → [Handler] ← Entity → [Dao/DB]

The client does requests against the server, this is deserialized to a RO.

This RO is then used by the service to do logic, this is where processes are done.

The service in turn works with the handlers, where short and transactional logic can be performed.

They in turn can use the DAO (Data Access Object) to persist and fetch data from the DB.

I'll dig deeper into the different layers, and what they are in charge of, and why they use the Objects they do to talk to the other layers.

The API

This is a facade, facing the client. Not all data needs to be transferred in order to do the requested action. For example, there could be fields on an object that are immutable, and there is no reason for the client to send them over when updating an object. Or there could be fields in the server that are only required for server logic, no reason to send them back via the API. This is why this layer works with ROs aka Representation Objects. They can have less fields, or aggregated fields, that can be used by the client.

This layer should be kept as “stupid” as you can, allowing the system to have multiple different types of APIs that call the same logic. The logic is based on the Service layer, and the API calls upon that layer to achieve its goals.

Service Layer

This is where most of the business logic lives, the different processes that constitute a system. These processes can be long, and can require multiple entities within the system. It's the service layer's responsibility to map the ROs into something more useful for handling the logic.

The service layer calls different Handlers, each with its own domain of responsibility to run the logic. These processes can be long, and should not be transactional (Create locks in the DB).

Handlers

Handlers have very well defined responsibilities, they usually work with only 1 entity, and they are the ones that can do transactional actions. They are also the only ones that talk to the DAO/DB.

As you know, DB transactions can cause locks. One of the things we strive for in good system design, is to minimize lock time and scope. This is why the handlers are separated from the services. In the handlers is where you will find the code most optimized, and single goal oriented.

DAO/DB

This is where persistence occurs. This layer is similar to the API layer in that it should be as simple and “stupid” as possible. Correct implementation of this allows us to simply (relatively) change DB by just re-implementing the DAO layer.

An example:

Lets take for example a process of publishing a new version of a post. This process is triggered by a request from the client, all that is needed is the post_id and version_number. So we have an RO with those fields only.

The process starts with some validation

Does that post_id exist?

Can the user requesting the publishing perform that action?

Is there a version_number like that?

Is it already published?

You can think of some more….

Now of course at the end of this, we want to transactionally update the is_published flag at the same time on the old version as the new. But during the time we are doing validations there is no reason to open a write transaction in the DB, and lock the entities. It is much better to do the validation process, call the handler just to do the swap, then continue after the transaction to create the result for the client.

This way we gained a few key points.

Shortest transaction time possible.

No connection to DB used if the request fails in the validation phase.

Code reuse - the method that publishes in the handler can later be used by different services (for example an automated time publisher).

Clear single responsibilities for each layer.

Practice what I preach

Over the years I have honed this layered design (I will get into it in later posts), but the basics remain. This code forced me to look at my code differently, and implement the layers, and I have to tell you, this has made my code much more readable and maintainable as well. It has also increased my development speed, because I can write each layer's interface, and then start filling in the methods.

I won't lie, it took me a while to get used to working this way, and even longer to get it right (I would put things in the handler that could have been in a service and split among multiple handlers), but it's a process. I urge you to try and split some code you are working on using these principals. Everyone has that 1 method that is so big that nobody wants to touch it. Go ahead, give it a go, I'm confident that after some time (and banging your head against the wall) you will be able to split it up and have much better code at the end.

Please check out my open source library and maybe even star it. I would appreciate it greatly!

 
Share this