Layers of abstraction

Eduardo Poleo
3 min readSep 14, 2016

--

Learning how to organize our code into logical layers of abstraction is critical to ensure developers sanity and scalability. As an example let’s imagine we are setting up a registration flow for a simple forum app. At the beginning the controller code that handles the user registration probably looked like this:

After a little while our application grew and so did the requirements of our registration process that now include:

  • Send a welcome email
  • Checking if the registration comes from an invite link. If so, then the new user should follow the existing user activity and the existing user should be rewarded
  • Check if a certain community milestone has been reached (e.g 50,000 users has been subscribed )

All of the sudden, our once minimalist controller action looks like this:

To understand what’s wrong with this code we should read the following paragraph:

The term automotive was created from Greek autos (self), and Latin motivus (of motion). In 1929 before the Great Depression, the world had 32,028,500 automobiles in use. Automobiles and other motor vehicles have to comply with a certain number of norms and regulations.

The lines above are probably grammatically correct and we can infer that they talk about the automotive industry. But the paragraph as a whole lacks coherence or consistency because it tries to convey too many ideas in one spot.

Our code has a similar problem. It tries to check off all the requirement in a single method!!. This is wrong for several reasons:

  • Violates the concerns of the controller whose main job is to re-route or render information to the user.
  • It is hard to test due to the amount of path and logic involved.
  • Most importantly it is hard for developers to read and reason about.

A “better” way to structure this code could be:

This is a lot more code and a lot more indirection, the code that was in 13 lines files is now spread out into 4 different files.

So Why is it worth it go through all this trouble? For the same reason we do not write a history essay in one paragraph: The concepts we are trying to express now form part of a cohesive story with each class having a well defined purpose:

  • UserSignupManager is a higher level class that manages the signup flow and provides error handling.
  • InvitationHandler handles the the follows relationships and invitations.
  • MilestoneChecker checks for milestones.

Other practical advantages are:

  • Testing becomes much easier because we have modular classes with well defined inputs and outputs, and a reduced amount of logic in them. Consequently, individual test file sizes also shrink.
  • We are able to more easily tackle and recognize edge-cases. Imagine how messy would have been if we have tried to handle persistance errors within the controller action.
  • We have stablished some patters that are more suitable for scaling our application up. If anyone needs to add a persitance action, change the behaviour of the follows relationships or simply add a new milestone they can just go straight into the corresponding methods and make their changes.

I hope you enjoyed the reading. Happy coding!

--

--