The Story Repeats Itself
When I started my programming journey, I was simply a Web Developer. I was responsible for both frontend and backend parts, communication between them, and often for deployment and maintenance as well. Over time, however, these roles began to specialize, and people started focusing only on certain parts of the development process. This specialization was necessary since the complexity of systems has been growing, making it harder to understand, develop, and maintain the entire software stack. This complexity stems from more than just business logic but also from the myriad of technologies, tools, libraries, and frameworks. However, it turns out that having many specialized professionals is sometimes not cost-effective for projects of a certain scale. This gap has been filled by frameworks like Remix and Next.js, which are full-stack frameworks offering both frontend and backend programming capabilities.
However, Here Lies the Problem…
The cycle has yet to come full circle. The emphasis has shifted. In the past, frontend applications were secondary to backend applications. The backend would generate HTML for the frontend, and over time, with the advent of browser scripts, this trend started to reverse. In many contemporary applications, the focus is now on the frontend. This shift depends on the type of project and the complexity of its components. Sometimes, we deal with a simple interface and a complex API, while other times, both are equally complex or simple. However, the problem that frameworks like Next.js solve is when we have a complex interface and a simple API, which can be compared to a data repository. These frameworks allow us to quickly program a complex frontend application and the necessary backend application. In such cases, it's advantageous to have a developer who can handle both sides, saving a lot of time and money by avoiding the need to create a separate API. In this scenario, a frontend developer is the obvious choice. However, with specialization towards the frontend, backend skills are often neglected, resulting in a backend that becomes unmanageable, with complexity growing and resembling spaghetti code, while the frontend code remains clean and consistent.
Why Does This Happen?
Frontend applications have a different nature compared to backend applications. The architecture is inherited from available frameworks and libraries, implicitly guiding the developer. Although this approach can backfire in the long run and become unmanageable, it's better than having no architecture at all, especially when we are unsure how to design one sensibly. This is the situation we face on the backend side with Next.js or Remix. These frameworks offer us an entry point to the backend, automating communication via AJAX so we don't have to manually integrate it. However, they don't provide us with a structure within which we can operate.
Let's Introduce Some Architecture!
Since the framework doesn't enforce it, we should ensure we have an architecture that structures our code. Our basis for consideration will be GRASP, or General Responsibility Assignment Software Patterns. It contains a set of 9 basic principles and responsibilities that we should apply in our code. For our purposes, let's select 4 responsibilities:
-
Information Expert - the object with all the information needed to perform a certain task.
-
Controller - the object that controls the flow of work.
-
Pure Fabrication - the object that performs tasks that are not part of the domain.
-
Creator - the object that creates other objects.
These 4 types of objects outline an obvious division that we can try to implement in our application:
-
Domain part, which will represent the concepts of our client's business. We can also think of it as concepts that could exist without the IT system, e.g., instead of a product catalog, we have a flyer sent by mail to the customer's mailbox.
-
Technical part, which is required to handle the domain part and exists solely for the IT system's needs.
First, we should identify the responsibilities above in our code and check if they are mixed together. This way, we will separate the technical part from the business logic, laying the foundation for further dividing our architecture into layers.
What Can Be Done?
The lack of enforced architecture often leads to a haphazard structure, resulting in code that is hard to maintain, untested, and challenging to develop. In the following article, we will look at what to pay attention to when creating an architecture: not to invent our own architectures from known templates, but to adapt them to our real needs and make the first architectural decisions.