Separating Front End and Back End Is Solid Design

Introduction

I have been thinking on how to build applications in a multidevice world.

Last week I talked about my experiments with Django and the way it proposes through its architecture and its API that web applications should be.

In Django land, web applications are multipage, and the view is rendered in the server through a template language with the final objective of being presented on a browser.

If the client of this application needs a state to be maintained or transformed, it is passed around through sessions or stored in the database.

The same thing is done in applications such as WordPress or another PHP framework similar to Django, such as Laravel.

While doing my experiments with Django, I missed a clear way to separate the view layer from the controller layer so that functionalities can be tested separately in an automated fashion.

It is possible of course, but additional complexity would need to be involved in its design.

I would have to use Cypress to do the end-to-end tests and add unit tests to concrete elements of the system, which would involve decoupling template rendering and the django controllers or “views”.

This past weekend, I had to develop a backend in Spring Boot whose purpose was to derive the price of a product from a set of rows in an H2 database as part of a technical inteview process.

I had worked with Spring Boot before, but on this project I was tasked with finding the right dependencies for everything, meaning that I had to choose the framework to do the end-to-end tests with, define the persistence or database layer, and figure out how to make everything fit.

I realised that it was much easier to deliver a stable and well-tested backend using the abstraction layer that a REST API provides than trying to build a full stack application with Django.

Another thing I also realised how flexible Spring Boot is for back end development, and how it can be integrated with almost any kind of datasource, which we will probably discuss in another article.

On this article we will explore this approach, which is to have a dedicated front-end and a dedicated back-end.

SOLID principles

I think that we all want to build applications with code that is maintainable and easy to work with.

The first recomendation a senior programmer will give a junior programmer before dumping a book of design patterns uppon its head is to follow the SOLID principles.

SOLID principles are a set of ethereal and philosofical set of intentions or “programmer wisdom” that sometimes can be mapped to concrete actions in your code that generally result in increased maintainability and decreased suffering.

Those principles are:

  • The Single Responsibility: A class should change only because one reason, which means that they are responsible for one aspect of the system.
  • The Open-Closed Principle: Entities should be open for extension, but closed for modification.
  • The Liskov Substitution Principle: Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
    • This is also known as design by contract, which we will talk about in this article.
  • The Interface Segregation Principle: Clients should not be forced to depend upon interfaces that they do not use.
  • The Dependency Inversion Principle: Depend on abstractions, not concretes.

These principles are usually applied to the world of object-oriented programming, but I think they can also be applied the more generic area of object-oriented design or architecture.

Separating the front end and the back end means that, you have two components where the front end is tasked with the presentation of the data and the state management of the user interface, while the backend is responsible for the access and management to the data, satisfying the principle of single responsability.

The front end and the back end will need to define a concrete interface to correctly integrate, which uppon itself is an abstraction, satisfying the dependency inversion principle.

A back end that satisfies an abstraction or API specification of some kind can have many different implementations, so the Liskov substitution principle also applies.

You can have a back end in Spring Boot, Golang, FastAPI or whatever you need. If they implement the same REST API, they should be interchangeable.

In the same way, a single back end can have multiple types of clients implemented in the technologies of their respective platforms, no matter if it is ReactJs, Vue.js or a native Android or iOS clients.

This separation will also allow each component to be tested with the native testing tools of their respective development ecosystems.

A client written in ReactJS or Vue.js can be tested using a tool such as Cypress by mocking this well established interface.

The backend service can be unit tested using its own native tools, such as JUnit in Java or RestAssured for Spring Boot endpoints.

Another reason for this separation is the specialization of roles in some organizations, although I am a strong believer that all developers should consider themselves fullstack.

Most of the time the kind of people that make the designer and the engineer are not easily found in the same person.

Usually the designer is able to do a little bit of code and is located in the front end, where it can take better decisions regarding the user experience of the product.

On the other end, the back end engineer is a more CS type which has to think in terms of data structures, algorithms, security, databases and scalability.

These are the reasons why I believe that the most SOLID way to do web apps is to make this separation, which has also been the choice of the market for the past 15 years of web development.

Design by contract

One tool we can use to guarantee that the REST API specification is both version controlled and understood by both the front end and the back end is Swagger.

At least in theory, you could also use this specification to generate stubs for the back end server and the front end clients.

However, the times I have used this to generate code it has given me problems.

I was using it to generate C++ endpoints, so maybe those stub generators are not as well maintained as for other languages, or they simply were in a beta version.

In any case, this is just a tool by which you can generate OpenAPI specifications that you can pass on to a freelancer or to keep in your large organization to guarantee integration with your existing systems and teams.

It is also a great tool to clearly communicate how an inteface should receive and send to other members of your team, establishing clear conditions and precoditions to the users of your code.

This could also be used inside a monolith via the use of interfaces, abstract classes and some kinds of design patterns that enforce that the data meets some postconditions such as a builder.

Ok, but no

Does this mean that you need to separate back-end and front-end in order to have a SOLID architecture?

Probably not. You just need to decouple the views and the controllers and test them independently.

You want to do this separation to provide a better user experience to your users.

So that they can have caches between you and your server, so that the state is maintained in the client and not in the server, which would reduce the server load in some use cases, or to use native client technologies instead of web or hybrid ones.

Conclusion

I am just exploring the posibilities with different technologies and figuring out which use cases are the most suited for them.

If you want to make a web-first application to access a database, and you want to use Python instead of PHP (for which you would prefer WordPress), then go with Django.

If instead you want to go mobile-first in the same way that Instagram did, which seems were the future for the consumer market for applications is, then you need separation of front end and back end and to specify an interface of some kind between them, which does not mean that there are better more creative solutions that are possible.

As with most things that are involved in engineering, it depends.

What you should always aim for however, is to guarantee that your system is resilient to changes, maintainable, and available to its users so that it “simply works”.

See you in the next one.

whoami

Jaime Romero is a software developer and cybersecurity expert operating in Western Europe.