Development teams often ask me, "How should we structure our application?" While the answer is never simple, it often involves a statement recommending to build an API, between your presentation layer and your application layer. Logically, you can split an application into three technical layers, presentation, application and services.
Presentation layer
The presentation layer in many applications is the User Interface. In web applications the user interface consists of HTML, CSS, and JavaScript, and it runs in the browser. In some applications, they require presentations in web, mobile and desktop.
Application layer
The application layer contains the logic of the application and runs on scalable servers or can run in a server-less environment. The application can also bridge the presentation layer and the services layer. The application can also be used to hide/secure secrets to handle secure access to services. Often times the application layer ends up just being a proxy between the presentation and service layer, don't fall into this trap. In a healthy future-proof or anti-fragile application the application layer is the most important layer and the majority of the code is made up of plain vanilla objects that have no dependencies on services or presentation. This loosely coupled model gives you the ability to create resilient applications.
Services layer
One or more application services, from data, storage, cacheing, search, billing, email, sms, social, etc. These services are the connections or integrations to systems that do general or generic tasks that are not specific or special to the application, but are required by the application in order for it to function properly.
When building applications, there are many patterns and practices to design and structure the application, and teams choose to blur the lines between these three logical layers. Using frameworks and cloud services can make it easy to create an application where you kind of proxy the services layer giving all the control and power to your presentation layer, or you exclude the application layer altogether and just implement presentation to services.
Building applications with APIs
When I say to build applications using APIs, does not mean directly using services like firebase, or amplify, or hyper63, those tools are best used when they are connected to the application layer, service frameworks exist to save your team development time when building and connecting to services. Application APIs, is the interface between your presentation layer and your application layer. Craft your application API to be specific to your business logic. So many frameworks that want you to use their conventions to sprinkle your business logic throughout the technical layers. Unknowingly to the team, this practice starts to scatter the business logic throughout the application, which indirectly creates very tight coupling between your presentation layer, application layer, and services layer.
At first, the seems to be highly productive because you are having to construct the presentation and the services layers.
Scattering your business logic between the presentation, application, and services layers, indirectly creates tight coupling throughout your application.
When introducing your application to users, change requests will start to flow. If the application has its business logic spread throughout the application, you will find yourself touching each layer of the application improve, adjust and satisfy the change request. With small apps this is not too challenging, but as features grow and the complexity increases the workflow of the entire application can be difficult to follow. This can lead to increased development effort, technical debt and unanticipated bugs. The development teams time and velocity starts to slow down, quality starts to decrease, and the chores and maintenance tasks start to grow.
As more features are requested the more business logic gets spread around your technical layers.
By spreading your business logic over all layers of the application it will lock the application into specific technologies, and migrations to newer technologies becomes challenging. As your application becomes successful, the need to extend your services due to scalability, security, or performance can seem impossible without a full rewrite.
Business Logic should live in one layer
Think of the application layer as a container of all of your business logic, abstract the special rules and design from your services by declaring your schemas and configuration of the services in the application layer.
Even naming tables and columns in a datastore service, you are placing specific business logic inside that service, and if something changes, you have to address the change in the datastore service, which usually requires a deployment task that can make deployments complex and hard to automate.
Define your schemas in your application layer
Instead of describing your tables and columns in the datastore service, define them in your application layer, make your datastore service generic, not specific to your application.
Even though you define your schemas in your application layer, you need to leverage strong validation libraries to make sure your data is clean and consistent, test for length, valid dates, and valid numbers, enumerated types. In other words, use a validation library that prevents bad data from entering your application.
Create specific APIs in your application layer
The next step is much harder, it requires design and strategy. This is the process of creating a boundary between your application layer and your presentation layer. You want your presentation layer to dispatch specific instructions to your application layer, and by designing a specific API to limit the capability of the presentation layer. Instead of giving the presentation layer full control of your services, only give the functionality they need to accomplish the task that the user needs to get done.
Sometimes you just don't know enough to get the API right, but if you start to create the boundaries between your technical layers. You can set your application up for success, by continuously refactoring the application boundaries and keeping your business logic in the Application Layer as vanilla objects with no service dependencies.
A popular pattern to keep your business logic dependency free is to leverage inversion of control, or dependency injection. This is the process of delivering an interface or abstraction to your business logic object instead of the business logic object depending on a service driver directly.
Continuous and Fast feedback loops
This design decision can result in creating a fast feedback loop for consistent turn around time in changing or creating features as you receive feedback to improve your application. As a result of this containment of your business logic, integration tests become highly effective and easy to accomplish.
Frameworks want to marry your business logic and presentation logic, guard against this, using frameworks can increase productivity, but abstract your business logic from the presentation layer.
This sounds great, but how do I go about doing this, when I am using framework x or library y. To keep your business logic separated from your framework takes design and strategic thinking.
Think about what is special and what is general, you want to push all of the general not specific functionality to either the presentation or the services. hyper63 gives you a common boundary to your services layer leveraging the ports and adapters pattern. To properly contain your business logic in your application layer and not let it leak into the presentation layer can be challenging. When trying to separate or isolate your specific business rules between your presentation and application layer, try to keep your presentation layer focused on the presentation and not business logic.
Caution: don't take this too far unless the requirements demand it. What is taking too far? For example, fonts, colors, internationalization, ux designs are not business logic, they are presentation logic and should reside in the presentation layer, unless your application demands the ability for the user to control these things do not add them to your business layer.
An example of leveraging APIs as boundaries
For example, in a todo application, you want to mark an item as completed. Instead of this being a full update where you could change the entire object.
PUT /todos/1
{"id": "1", "text": "Pickup the Laundry", "completed": true }
Create an API that is more specific:
POST /todos/1/$mark_complete
This takes the responsibility of marking completed property from false to true from the presentation layer to the application layer. The presentation layer, just has to know the todo id and it can make the call to the application layer to mark it complete. The application layer can provide clear feedback with straight forward outcomes: Success, Already Complete, Error, Not Found, etc.
As a result, it makes your presentation implementation more declarative and gives the presentation layer a strong boundary with rules on what the presentation layer can and can not do. The more you take away choices from the presentation layer, the more you move or contain your business logic in your application layer. And as change requests come in the less moving parts you have to touch to satisfy the new requirement.
This practice is easier said than done, but if you try to catch yourself from giving too much control to the presentation, you will notice over time that your business logic begins to settle in the application layer.
Summary
So to summarize, when building applications think about where your business logic goes, use APIs to create a boundary between your technical layers, leverage services and frameworks for general work, and focus your time and energy on specific work towards your business or product. Finally, don't give too much control to the presentation layer, only let it do specific, direct functionality.
Thank you for reading, I hope you found this article helpful, if you have any questions or comments, we would love to hear from you. You can post a comment on twitter tagging @twilson63 or @hyper63
Photo by Mark Tegethoff on Unsplash