N-tier architecture divides an application into logical layers and physical tiers. Layers are a way to separate responsibilities and manage dependencies. Each layer has a specific responsibility. A higher layer can use services in a lower layer, but not the other way around.
Tiers are physically separated, running on separate machines. A tier can call to another tier directly, or use asynchronous messaging. Although each layer might be hosted in its own tier, that’s not required. Several layers might be hosted on the same tier. Physically separating the tiers improves scalability and resiliency and adds latency from the additional network communication.
An N-tier architecture can be of two types:
A closed-layer architecture limits the dependencies between layers. However, it might create unnecessary network traffic, if one layer simply passes requests along to the next layer.
Let’s look at some examples of N-Tier architecture:
3-Tier is widely used and consists of the following different layers:
In this architecture, the presentation layer runs on the client and communicates with a data store. There is no business logic layer or immediate layer between client and server.
It is the simplest one as it is equivalent to running the application on a personal computer. All of the required components for an application to run are on a single application or server.
Here are some advantages of using N-tier architecture:
Below are some disadvantages of N-tier architecture:
A message broker is a software that enables applications, systems, and services to communicate with each other and exchange information. The message broker does this by translating messages between formal messaging protocols. This allows interdependent services to “talk” with one another directly, even if they were written in different languages or implemented on different platforms.
Message brokers can validate, store, route, and deliver messages to the appropriate destinations. They serve as intermediaries between other applications, allowing senders to issue messages without knowing where the receivers are, whether or not they are active, or how many of them there are. This facilitates the decoupling of processes and services within systems.
Message brokers offer two basic message distribution patterns or messaging styles:
We will discuss these messaging patterns in detail in the later tutorials.
Message brokers can support two or more messaging patterns, including message queues and pub/sub, while event streaming platforms only offer pub/sub-style distribution patterns. Designed for use with high volumes of messages, event streaming platforms are readily scalable. They’re capable of ordering streams of records into categories called topics and storing them for a predetermined amount of time. Unlike message brokers, however, event streaming platforms cannot guarantee message delivery or track which consumers have received the messages.
Event streaming platforms offer more scalability than message brokers but fewer features that ensure fault tolerance like message resending, as well as more limited message routing and queueing capabilities.
Enterprise Service Bus (ESB) infrastructure is complex and can be challenging to integrate and expensive to maintain. It’s difficult to troubleshoot them when problems occur in production environments, they’re not easy to scale, and updating is tedious.
Whereas message brokers are a “lightweight” alternative to ESBs that provide similar functionality, a mechanism for inter-service communication, at a lower cost. They’re well-suited for use in the microservices architectures that have become more prevalent as ESBs have fallen out of favor.
Here are some commonly used message brokers:
A message queue is a form of service-to-service communication that facilitates asynchronous communication. It asynchronously receives messages from producers and sends them to consumers.
Queues are used to effectively manage requests in large-scale distributed systems. In small systems with minimal processing loads and small databases, writes can be predictably fast. However, in more complex and large systems writes can take an almost non-deterministic amount of time.
Messages are stored in the queue until they are processed and deleted. Each message is processed only once by a single consumer. Here’s how it works:
Let’s discuss some advantages of using a message queue:
Now, let’s discuss some desired features of message queues:
Most message queues provide both push and pull options for retrieving messages. Pull means continuously querying the queue for new messages. Push means that a consumer is notified when a message is available. We can also use long-polling to allow pulls to wait a specified amount of time for new messages to arrive.
In these queues, the oldest (or first) entry, sometimes called the “head” of the queue, is processed first.
Many message queues support setting a specific delivery time for a message. If we need to have a common delay for all messages, we can set up a delay queue.
Message queues may store multiple copies of messages for redundancy and high availability, and resend messages in the event of communication failures or errors to ensure they are delivered at least once.
When duplicates can’t be tolerated, FIFO (first-in-first-out) message queues will make sure that each message is delivered exactly once (and only once) by filtering out duplicates automatically.
A dead-letter queue is a queue to which other queues can send messages that can’t be processed successfully. This makes it easy to set them aside for further inspection without blocking the queue processing or spending CPU cycles on a message that might never be consumed successfully.
Most message queues provide best-effort ordering which ensures that messages are generally delivered in the same order as they’re sent and that a message is delivered at least once.
Poison pills are special messages that can be received, but not processed. They are a mechanism used in order to signal a consumer to end its work so it is no longer waiting for new inputs, and are similar to closing a socket in a client/server model.
Message queues will authenticate applications that try to access the queue, this allows us to encrypt messages over the network as well as in the queue itself.
Tasks queues receive tasks and their related data, run them, then deliver their results. They can support scheduling and can be used to run computationally-intensive jobs in the background.
If queues start to grow significantly, the queue size can become larger than memory, resulting in cache misses, disk reads, and even slower performance. Backpressure can help by limiting the queue size, thereby maintaining a high throughput rate and good response times for jobs already in the queue. Once the queue fills up, clients get a server busy or HTTP 503 status code to try again later. Clients can retry the request at a later time, perhaps with exponential backoff strategy.
Following are some widely used message queues:
Similar to a message queue, publish-subscribe is also a form of service-to-service communication that facilitates asynchronous communication. In a pub/sub model, any message published to a topic is pushed immediately to all the subscribers of the topic.
The subscribers to the message topic often perform different functions, and can each do something different with the message in parallel. The publisher doesn’t need to know who is using the information that it is broadcasting, and the subscribers don’t need to know where the message comes from. This style of messaging is a bit different than message queues, where the component that sends the message often knows the destination it is sending to.
Unlike message queues, which batch messages until they are retrieved, message topics transfer messages with little or no queuing and push them out immediately to all subscribers. Here’s how it works:
Let’s discuss some advantages of using publish-subscribe:
Now, let’s discuss some desired features of publish-subscribe:
Pub/Sub messaging instantly pushes asynchronous event notifications when messages are published to the message topic. Subscribers are notified when a message is available.
In the Publish-Subscribe model, topics can typically connect to multiple types of endpoints, such as message queues, serverless functions, HTTP servers, etc.
This scenario happens when a message is sent to a topic and then replicated and pushed to multiple endpoints. Fanout provides asynchronous event notifications which in turn allows for parallel processing.
This feature empowers the subscriber to create a message filtering policy so that it will only get the notifications it is interested in, as opposed to receiving every single message posted to the topic.
Pub/Sub messaging services often provide very high durability, and at least once delivery, by storing copies of the same message on multiple servers.
Message topics authenticate applications that try to publish content, this allows us to use encrypted endpoints and encrypt messages in transit over the network.
Here are some commonly used publish-subscribe technologies:
An Enterprise Service Bus (ESB) is an architectural pattern whereby a centralized software component performs integrations between applications. It performs transformations of data models, handles connectivity, performs message routing, converts communication protocols, and potentially manages the composition of multiple requests. The ESB can make these integrations and transformations available as a service interface for reuse by new applications.
In theory, a centralized ESB offers the potential to standardize and dramatically simplify communication, messaging, and integration between services across the enterprise. Here are some advantages of using an ESB:
While ESBs were deployed successfully in many organizations, in many other organizations the ESB came to be seen as a bottleneck. Here are some disadvantages of using an ESB:
Below are some widely used Enterprise Service Bus (ESB) technologies:
A monolith is a self-contained and independent application. It is built as a single unit and is responsible for not just a particular task, but can perform every step needed to satisfy a business need.
Following are some advantages of monoliths:
Some common disadvantages of monoliths are:
A Modular Monolith is an approach where we build and deploy a single application (that’s the Monolith part), but we build it in a way that breaks up the code into independent modules for each of the features needed in our application.
This approach reduces the dependencies of a module in such as way that we can enhance or change a module without affecting other modules. When done right, this can be really beneficial in the long term as it reduces the complexity that comes with maintaining a monolith as the system grows.
A microservices architecture consists of a collection of small, autonomous services where each service is self-contained and should implement a single business capability within a bounded context. A bounded context is a natural division of business logic that provides an explicit boundary within which a domain model exists.
Each service has a separate codebase, which can be managed by a small development team. Services can be deployed independently and a team can update an existing service without rebuilding and redeploying the entire application.
Services are responsible for persisting their own data or external state (database per service). This differs from the traditional model, where a separate data layer handles data persistence.
The microservices architecture style has the following characteristics:
Here are some advantages of microservices architecture:
Microservices architecture brings its own set of challenges:
Let’s discuss some microservices best practices:
Below are some common pitfalls of microservices architecture:
Distributed Monolith is a system that resembles the microservices architecture but is tightly coupled within itself like a monolithic application. Adopting microservices architecture comes with a lot of advantages. But while making one, there are good chances that we might end up with a distributed monolith.
Our microservices are just a distributed monolith if any of these apply to it:
One of the primary reasons to build an application using microservices architecture is to have scalability. Therefore, microservices should have loosely coupled services which enable every service to be independent. The distributed monolith architecture takes this away and causes most components to depend on one another, increasing design complexity.
You might have seen Service-oriented architecture (SOA) mentioned around the internet, sometimes even interchangeably with microservices, but they are different from each other and the main distinction between the two approaches comes down to scope.
Service-oriented architecture (SOA) defines a way to make software components reusable via service interfaces. These interfaces utilize common communication standards and focus on maximizing application service reusability whereas microservices are built as a collection of various smallest independent service units focused on team autonomy and decoupling.
So, you might be wondering, monoliths seem like a bad idea to begin with, why would anyone use that?
Well, it depends. While each approach has its own advantages and disadvantages, it is advised to start with a monolith when building a new system. It is important to understand, that microservices are not a silver bullet, instead, they solve an organizational problem. Microservices architecture is about your organizational priorities and team as much as it’s about technology.
Before making the decision to move to microservices architecture, you need to ask yourself questions like:
If your application does not require to be broken down into microservices, you don’t need this. There is no absolute necessity that all applications should be broken down into microservices.
We frequently draw inspiration from companies such as Netflix and their use of microservices, but we overlook the fact that we are not Netflix. They went through a lot of iterations and models before they had a market-ready solution, and this architecture became acceptable for them when they identified and solved the problem they were trying to tackle.
That’s why it’s essential to understand in-depth if your business actually needs microservices. What I’m trying to say is microservices are solutions to complex concerns and if your business doesn’t have complex issues, you don’t need them.
Event-Driven Architecture (EDA) is about using events as a way to communicate within a system. Generally, leveraging a message broker to publish and consume events asynchronously. The publisher is unaware of who is consuming an event and the consumers are unaware of each other. Event-Driven Architecture is simply a way of achieving loose coupling between services within a system.
An event is a data point that represents state changes in a system. It doesn’t specify what should happen and how the change should modify the system, it only notifies the system of a particular state change. When a user makes an action, they trigger an event.
Event-driven architectures have three key components:
Note: Dots in the diagram represents different events in the system.
There are several ways to implement the event-driven architecture, and which method we use depends on the use case but here are some common examples:
Note: Each of these methods is discussed separately.
Let’s discuss some advantages:
Here are some challenges of event-drive architecture:
Below are some common use cases where event-driven architectures are beneficial:
Here are some widely used technologies for implementing event-driven architectures:
Instead of storing just the current state of the data in a domain, use an append-only store to record the full series of actions taken on that data. The store acts as the system of record and can be used to materialize the domain objects.
This can simplify tasks in complex domains, by avoiding the need to synchronize the data model and the business domain, while improving performance, scalability, and responsiveness. It can also provide consistency for transactional data, and maintain full audit trails and history that can enable compensating actions.
Event sourcing is seemingly constantly being confused with Event-driven Architecture (EDA). Event-driven architecture is about using events to communicate between service boundaries. Generally, leveraging a message broker to publish and consume events asynchronously within other boundaries.
Whereas, event sourcing is about using events as a state, which is a different approach to storing data. Rather than storing the current state, we’re instead going to be storing events. Also, event sourcing is one of the several patterns to implement an event-driven architecture.
Let’s discuss some advantages of using event sourcing:
Following are the disadvantages of event sourcing:
Command Query Responsibility Segregation (CQRS) is an architectural pattern that divides a system’s actions into commands and queries. It was first described by Greg Young.
In CQRS, a command is an instruction, a directive to perform a specific task. It is an intention to change something and doesn’t return a value, only an indication of success or failure. And, a query is a request for information that doesn’t change the system’s state or cause any side effects.
The core principle of CQRS is the separation of commands and queries. They perform fundamentally different roles within a system, and separating them means that each can be optimized as needed, which distributed systems can really benefit from.
The CQRS pattern is often used along with the Event Sourcing pattern. CQRS-based systems use separate read and write data models, each tailored to relevant tasks and often located in physically separate stores.
When used with the Event Sourcing pattern, the store of events is the write model and is the official source of information. The read model of a CQRS-based system provides materialized views of the data, typically as highly denormalized views.
Let’s discuss some advantages of CQRS:
Below are some disadvantages of CQRS:
Here are some scenarios where CQRS will be helpful:
The API Gateway is an API management tool that sits between a client and a collection of backend services. It is a single entry point into a system that encapsulates the internal system architecture and provides an API that is tailored to each client. It also has other responsibilities such as authentication, monitoring, load balancing, caching, throttling, logging, etc.
The granularity of APIs provided by microservices is often different than what a client needs. Microservices typically provide fine-grained APIs, which means that clients need to interact with multiple services. Hence, an API gateway can provide a single entry point for all clients with some additional features and better management.
Below are some desired features of an API Gateway:
Let’s look at some advantages of using an API Gateway:
Here are some possible disadvantages of an API Gateway:
In the Backend For Frontend (BFF) pattern, we create separate backend services to be consumed by specific frontend applications or interfaces. This pattern is useful when we want to avoid customizing a single backend for multiple interfaces. This pattern was first described by Sam Newman.
Also, sometimes the output of data returned by the microservices to the front end is not in the exact format or filtered as needed by the front end. To solve this issue, the frontend should have some logic to reformat the data, and therefore, we can use BFF to shift some of this logic to the intermediate layer.
The primary function of the backend for the frontend pattern is to get the required data from the appropriate service, format the data, and sent it to the frontend.
GraphQL performs really well as a backend for frontend (BFF).
We should consider using a Backend For Frontend (BFF) pattern when:
Following are some widely used gateways technologies:
A good API design is always a crucial part of any system. But it is also important to pick the right API technology. So, in this tutorial, we will briefly discuss different API technologies such as REST, GraphQL, and gRPC.
Before we even get into API technologies, let’s first understand what is an API.
API stands for Application Programming Interface. It is a set of definitions and protocols for building and integrating application software. It’s sometimes referred to as a contract between an information provider and an information user establishing the content required from the producer and the content required by the consumer.
In other words, if you want to interact with a computer or system to retrieve information or perform a function, an API helps you communicate what you want to that system so it can understand and complete the request.
A REST API (also known as RESTful API) is an application programming interface that conforms to the constraints of REST architectural style and allows for interaction with RESTful web services. REST stands for Representational State Transfer and it was first introduced by Roy Fielding in the year 2000.
In REST API, the fundamental unit is a resource.
Let’s discuss some concepts of a RESTful API.
Constraints
In order for an API to be considered RESTful, it has to conform to these architectural constraints:
HTTP Verbs
HTTP defines a set of request methods to indicate the desired action to be performed for a given resource. Although they can also be nouns, these request methods are sometimes referred to as HTTP verbs. Each of them implements a different semantic, but some common features are shared by a group of them.
Below are some commonly used HTTP verbs:
GET
request, but without the response body.HTTP response codes
HTTP response status codes indicate whether a specific HTTP request has been successfully completed.
There are five classes defined by the standard:
For example, HTTP 200 means that the request was successful.
Let’s discuss some advantages of REST API:
Let’s discuss some disadvantages of REST API:
REST APIs are pretty much used universally and are the default standard for designing APIs. Overall REST APIs are quite flexible and can fit almost all scenarios.
Here’s an example usage of a REST API that operates on a users resource.
URI | HTTP verb | Description |
---|---|---|
/users | GET | Get all users |
/users/{id} | GET | Get a user by id |
/users | POST | Add a new user |
/users/{id} | PATCH | Update a user by id |
/users/{id} | DELETE | Delete a user by id |
There is so much more to learn when it comes to REST APIs, I will highly recommend looking into Hypermedia as the Engine of Application State (HATEOAS).
GraphQL is a query language and server-side runtime for APIs that prioritizes giving clients exactly the data they request and no more. It was developed by Facebook and later open-sourced in 2015.
GraphQL is designed to make APIs fast, flexible, and developer-friendly. Additionally, GraphQL gives API maintainers the flexibility to add or deprecate fields without impacting existing queries. Developers can build APIs with whatever methods they prefer, and the GraphQL specification will ensure they function in predictable ways to clients.
In GraphQL, the fundamental unit is a query.
Let’s briefly discuss some key concepts in GraphQL:
Schema
A GraphQL schema describes the functionality clients can utilize once they connect to the GraphQL server.
Queries
A query is a request made by the client. It can consist of fields and arguments for the query. The operation type of a query can also be a mutation which provides a way to modify server-side data.
Resolvers
Resolver is a collection of functions that generate responses for a GraphQL query. In simple terms, a resolver acts as a GraphQL query handler.
Let’s discuss some advantages of GraphQL:
Let’s discuss some disadvantages of GraphQL:
GraphQL proves to be essential in the following scenarios:
Here’s a GraphQL schema that defines a User
type and a
Query
type.
type Query {
getUser: User
}
type User {
ID
id: String
name: String
city: String
state: }
Using the above schema, the client can request the required fields easily without having to fetch the entire resource or guess what the API might return.
{
getUser {
id
name
city
} }
This will give the following response to the client.
{
"getUser": {
"id": 123,
"name": "Karan",
"city": "San Francisco"
}
}
Learn more about GraphQL at graphql.org.
gRPC is a modern open-source high-performance Remote Procedure Call (RPC) framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking, authentication and much more.
Let’s discuss some key concepts of gRPC.
Protocol buffers
Protocol buffers provide a language and platform-neutral extensible mechanism for serializing structured data in a forward and backward-compatible way. It’s like JSON, except it’s smaller and faster, and it generates native language bindings.
Service definition
Like many RPC systems, gRPC is based on the idea of defining a service and specifying the methods that can be called remotely with their parameters and return types. gRPC uses protocol buffers as the Interface Definition Language (IDL) for describing both the service interface and the structure of the payload messages.
Let’s discuss some advantages of gRPC:
Let’s discuss some disadvantages of gRPC:
Below are some good use cases for gRPC:
Here’s a basic example of a gRPC service defined in a
*.proto
file. Using this definition, we can easily code
generate the HelloService
service in the programming
language of our choice.
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string greeting = 1;
}
message HelloResponse {
string reply = 1;
}
Now that we know how these API designing techniques work, let’s compare them based on the following parameters:
Type | Coupling | Chattiness | Performance | Complexity | Caching | Codegen | Discoverability | Versioning |
---|---|---|---|---|---|---|---|---|
REST | Low | High | Good | Medium | Great | Bad | Good | Easy |
GraphQL | Medium | Low | Good | High | Custom | Good | Good | Custom |
gRPC | High | Medium | Great | Low | Custom | Great | Bad | Hard |
Well, the answer is none of them. There is no silver bullet as each of these technologies has its own advantages and disadvantages. Users only care about using our APIs in a consistent way, so make sure to focus on your domain and requirements when designing your API.
Web applications were initially developed around a client-server model, where the web client is always the initiator of transactions like requesting data from the server. Thus, there was no mechanism for the server to independently send, or push, data to the client without the client first making a request. Let’s discuss some approaches to overcome this problem.
HTTP Long polling is a technique used to push information to a client as soon as possible from the server. As a result, the server does not have to wait for the client to send a request.
In Long polling, the server does not close the connection once it receives a request from the client. Instead, the server responds only if any new message is available or a timeout threshold is reached.
Once the client receives a response, it immediately sends a new request to the server to have a new pending connection to send data to the client, and the operation is repeated. With this approach, the server emulates a real-time server push feature.
Let’s understand how long polling works:
Here are some advantages of long polling:
A major downside of long polling is that it is usually not scalable. Below are some of the other reasons:
WebSocket provides full-duplex communication channels over a single TCP connection. It is a persistent connection between a client and a server that both parties can use to start sending data at any time.
The client establishes a WebSocket connection through a process known as the WebSocket handshake. If the process succeeds, then the server and client can exchange data in both directions at any time. The WebSocket protocol enables the communication between a client and a server with lower overheads, facilitating real-time data transfer from and to the server.
This is made possible by providing a standardized way for the server to send content to the client without being asked and allowing for messages to be passed back and forth while keeping the connection open.
Let’s understand how WebSockets work:
ws://
).Below are some advantages of WebSockets:
Let’s discuss some disadvantages of WebSockets:
Server-Sent Events (SSE) is a way of establishing long-term communication between client and server that enables the server to proactively push data to the client.
It is unidirectional, meaning once the client sends the request it can only receive the responses without the ability to send new requests over the same connection.
Let’s understand how server-sent events work: