From Request to Database: Understanding the Three-Layer Architecture in API Development
I’ve been working with APIs for a good while know and one thing that I’ve heard a lot from more experienced engineers, within my company and on the internet, is that we should split our API into three layers: API / Service / Repository.
And being honest here, as a less-experienced junior at the start of my career, I just listened and executed 🤷♂, without taking the time to understand and go into much dept onto why did we do some things the way we did them. And in my opinion, that’s totally fine at the start of your career, as your main focus is to learn how to write progressively prettier code 📈.
With that said, as you move on from just learning how to code, you start to question yourself about a lot of things, one of which for me was exactly the topic that I’ll we writing about here - why should we split the API in 3 layers ❓❕ Socrates said it best:
Why should we consider the three-layer architecture?
The short answer is that we want to take advantage of the benefits coming from this API architecture: separation of concerns, modularity, reusability and testability. Of course, there are some drawbacks: increased application complexity and performance overhead, but IMO the pros way outweigh the cons!
🚨 There is sometimes confusion between the terms “three-layer architecture” and “three-tier architecture,” but I have my own distinction for them. In my understanding, a layer refers to a specific component within the backend application, while a tier represents a self-contained unit that can be deployed independently and has its own infrastructure. Different teams can work on each tier separately. Amazon and IBM provide their own explanations of the three-tier architecture in their respective overviews. Here is an infographic illustrating the three-tier architecture: 👇
Let’s look at each component of the three
Let’s put some context in order for better understanding:
Imagine we have an Amazon like e-commerce website and the end-user is trying to make an order. He surfed trough our website and selected a few items that are now in his basket, ready to be ordered. For this he clicks on the ‘Order Now’ button, which triggers a request to our backend servers.
For this exercise, let’s assume that we have developed our backend server using Java and utilized Spring Boot to create the APIs.
It is important to note that the three-layer architecture can be implemented with any programming language and framework! The fundamental concept of separation and organization of responsibilities remains consistent across different technology stacks.
API Layer
Upon receiving the ‘Order Now’ request, the API layer comes into play. It handles the incoming request, performs any necessary input validation, and maps the request data to the appropriate format (in most cases - JSON format). The API layer is responsible for exposing endpoints that the client applications can interact with. In our example, it would handle the request for placing an order. Here’s a simplified example of an API endpoint for placing an order using Spring Boot:
@RestController
@RequestMapping("/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping
public ResponseEntity<String> placeOrder(@RequestBody OrderRequest orderRequest) {
// Perform any additional validation or data mapping
orderService.processOrder(orderRequest);
return ResponseEntity.ok("Order placed successfully");
}
}
Service Layer
Once the request is received by the API layer, it delegates the order processing logic to the service layer. The service layer encapsulates the business logic and acts as an intermediary between the API and the repository layer. In our example, it would handle tasks such as calculating the total order amount, checking product availability, and updating the inventory.
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private InventoryService inventoryService;
public void processOrder(OrderRequest orderRequest) {
// Perform business logic such as calculating total amount, validating items, etc.
// Update inventory using the inventoryService
// Save the order to the database using the orderRepository
}
}
Repository Layer
The third layer is the repository layer and it’s responsible for data access and persistence. It interacts directly with the database or any other data storage mechanism. In our example, the repository layer would handle tasks such as storing order details, retrieving product information, and managing the inventory. A simplified example of an order repository using Spring Data JPA:
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
// Additional methods for querying and managing orders
}
A deeper dive into the benefits and drawbacks
The Benefits 🟢
➕ Separation of concerns: By utilizing this architectural design, we separate the three layers so that each one of them has a clear separation from the others and service it’s purpose in the API. The API for the interaction with the outside world, the service layer for processing business logic and serving as a proxy between the presentation and database layers, and the repository layer for the database interaction. This separation makes the codebase more modular, easier to understand, and maintainable.
➕ Code Reusability/Modularity: This architecture allows us to design our components with modularity, making the services sharable among multiple API endpoints, and repositories reusable by the different services. This promotes code reusability, reducing duplication and improving development efficiency.
➕ Testability: Each layer can be tested in isolation, which allows for unit testing of the business logic and integration testing of the components interactions, without relying on external dependencies. Mocking or substituting specific layers during testing becomes easier, resulting in more comprehensive and reliable tests (for example, using interfaces as a contract between the service methods and the API layer mkaes testing easier).
➕ Maintainability: With clear boundaries between layers, maintaining and extending the application becomes more manageable. Changes in one layer are less likely to have a significant impact on other layers, reducing the risk of unwanted consequences.
The Drawbacks 🔴
➖ Complexity: Managing multiple layers and the interactions that are a byproduct of such architecture, brings more complexity when compared to a more straightforward approach of the API design.
➖ Performance overhead: Although negligible, there is a performance impact when you introduce more method invocations and data transformations with this approach.
➖ There are drawbacks such as new developers having to spend time learning about the way of implementation or the general additional development time needed to implement the three-layered architecture, but these come with everything new and shouldn’t be a serious bottleneck. Also, in most cases, the benefits far outweigh these last drawbacks.
✍️ I hope this article helped some of you gain or refresh their view on the way most modern APIs are implemented. Feel free to drop a comment if you have any questions or feedback you want to share! ❤️
Keep pushing and have a productive day! 🚀