Now with AI writing most of the code, software architecture has become a must-nown interview topic even for mid-level roles. The more you know about it, the more senior you'll come across in technical interviews. So today we will go through every software architecture pattern for monoliths microservices back and for front ends eventdriven and hexagonal plus some new AIdriven styles like the zeo token architecture and the architecture of an AI agent. By the end, you know exactly what problem each of the architecture styles solves. What are the trade-offs and when to pick it in production or in a system design interview. Let's start with number one, the monolith architecture. And in a monolith, we keep everything together. Both the back end, the front end, the business logic, they're all together in a single server right here. The only
thing that's not in the server itself, it's the database. And in this case, we have a two tire architecture. That means those two things are separated physically. And the reason for that is because it's much better for scalability and deployment purposes to have your database separated from the server. Now, if you ever worked with MBC frameworks like Ruby on Rails, Jungo or Apps.net back in the days, you probably work with a monolith where the server receive requests. It will run some logic and then send back pre-rendered HTML. The way you usually structure code in our monolith, it's following the MVC pattern, the model view controller. That means we'll have a core piece which is our controller that has the business logic and this is the one that will produce the view. So whenever a request comes in to get a certain page, the controller will run some logic. It will
call the model which makes the calls to the database and then with the data we'll put together the view and then send that back as plain HTML. And this was the way most web applications were built before single page applications came around. Now with the explosion of JavaScript, we started building single page applications using frameworks like React, Vue or Angular. And we also split the back end from the front end. And this is the kind of application most of us are familiar with. We have a front end that might be client rendered or server rendered. And then we have a server that exposes an API. And so the front end will make requests to the API and then receive back responses. And then finally, we still have our database over here. One of the most important things about this setup is the API. And one of the most important decision you
have to make is which API to use. Now there's mainly three types of APIs. There's REST APIs, graphical APIs, and GIC. The most useful ones when we communicate between the back end and the front end are rest and graphql. REST, it's perfect if you want something simple and robust and graph is good when you have different front-end clients because it prevents overfetching or underfetching. This video is focused on the architecture side of things. But if you want to know more about APIs, make sure you subscribe because I am preparing an API design video and you'll get the notification as soon as it comes
out. By the way, if you're preparing for an interview and you want to find your technical gaps, make sure you take our free technical assessment. The link is in the comments. It's built with real interview questions and it will tell you exactly what your technical gaps are. Now, as our backing girls, we might think about splitting it into modules. And that's where the third type of architecture comes in, which is the modular monolith. What we do here is we try to enforce strict boundaries across all those different modules. So, different teams can work in parallel and we don't have tight coupling. That means we can extend and add functionality with
less code changes. The same mentality we can apply to our front end monolith and start to also splitting our front end in different reusable libraries or different pages or different features. So both our back end and our front end are modular and modules are independent units with bounded context that have a specific public API and hide the implementation details of exactly what they do and that allows us to more efficiently develop the application and have less bugs. Now you can apply the same modularity also in your database and start splitting your data according to the modules you have in your modular back end. So it becomes easier for different teams to have clear boundaries and work in parallel. They still all deploy the same application and if someone breaks a database, the whole
server will go down. The last optimization we can do to our modular monolith is to add some internal services. Those will contain shared functionality. So for example, let's imagine we are building an e-commerce store and we have different products. We have a module for shipment. We have a module for payments. At some point all those modules might send an email to the user or the merchant and we can encapsulate the logic of sending an email in an email service that also has some data storage to maybe store the emails that it has sent or email templates. So this allows us to introduce this serviceoriented mentality where we can abstract away a lot of functionality under a simple API and that API can be used by by all the others modules and this follows the OP principles of encapsulation abstraction
and the solid principle of separation of concerns. So I code it's cleaner, easier to extend and more maintainable. Now the problem with the module monolith is that as we start taking in more and more traffic and adding more and more functionality things will start breaking. We might have a specific feature that gets a lot of traffic or it needs a different database and it just all becomes harder. The same problem we have with the database. We're storing everything in a single place. If someone breaks the schema, the whole thing goes down and it's also becoming more and more expensive to run. Which brings us
to the next architecture style which is microervices. Then microservices we take that modular backend monolith and we split it in independent services. Those services are totally isolated. They run on different servers. They expose a different API and they can have different databases. You could even switch the runtime. So you could have a service that's for payments that maybe runs Java and a service for analytics which will run NodeJS. So you get a lot more flexibility and also if something breaks you make sure that the whole system does not go down. Now distributing your system always comes with these advantages and in this case that's what we refer to when we talk about the microservices tax stacks and this is basically the cost of having to run all those services independently and provision all the infrastructure having to do in this case six deployments
instead of one and we'll also have to add some extra infrastructure pieces to make up for some of the disadvantages that the microser architecture has. The last point with microservices is that if we remember the cap terium, we know that in a distributed system that is now partitioned over the network, we can only choose one of the following guarantee. We can either be consistent or available. That means if we want strong consistency in a microser architecture, we will be slower. So let's imagine we have a transaction that involves three microservices. We will need to wait until that transaction is done. Make sure there was no failure and then finally return a success message and that takes a lot longer than it used to take in a monolith. And so remember that latency in microservices it's a real problem. Now one of the
optimizations we can do to our micros service architecture is to add an API gateway. And so an API gateway will basically be the gate to the rest of your services. And we can deploy all of them in a VPC. That's a virtual private cloud. And the advantage here is that none of those services are now accessible from the outer world. Whenever you have to call a micros service, you need to go through our API gateway. The advantage here is that we can have the API gateway handling things like HTTPS, authentication, security, rate limiting. And so a lot of the functionality that we had to implement in every single micros service, we have to implement only once. And so request would come in, it will hit the API gateway. The API gateway will do the HTTPS handshake. It will make sure that the client is not attacking us with rate limiting. And finally, it will forward a
request to a downstream micros service. And as you see here, we can use HTTP for the request between the API gateway and the microservices themselves because HTTP is a lot faster. And inside this protected VPC, we don't have such hard requirements on security because again the access here is very restricted and controlled by the API gateway. So as soon as you get a lot of microservices, one of the cheaper way to make them faster but also simpler to develop is to add an API gateway. The advantage for somebody working in the backend team is that if you're building the analytics microser, you don't need to worry about HTTPS. You don't need to worry about odd or caching or even rate limiting. you just focus on the business logic that your specific micros service has to contain. And by the way, if you're a software engineer and you're looking for the next position, we have a special
program where we work with you to get you hired in 90 days or less or you don't pay. If you want to know the details, the link is in the comments. The next optimization we can add to our microser architecture, it's backends for front ends. So as your system scales, you might get different users. Some of them are on the web. Some of them are mobile users. Maybe you have back office users. Those are business users depending on whatever you're building. Imagine in a case of e-commerce, we have the store itself. But then we also have an application for merchants. We might have an application for the inventory.
And all these different applications, they have different data requirements. They expect data from endpoints in different ways. And if they all have to request the same API will have a lot of problems. One of the most common is that an endpoint for a desktop application will return a lot more data than what the mobile application needs. And to solve this, we can build backend for front ends. The back end for front end, as the name says, it's a backend for a specific front end. So we could have a web backend for front end that's built for our web application. And this web back end for front end might expose a
graphical API which is much easier to consume from a client. And it's usually built by the front-end team. And the advantage here is that if the front end team needs to release a feature and they need API changes, they can do them themselves very fast. They have control over this, they own the whole thing and backend engineers can focus on the specific logic that the microser they work on has. So this gives us a lot of development speed. It also makes it easier to query the back end because as a front- end engineer, I just need to write one single call to the back end for front end and I don't need to worry
all the time about all these other calls, especially if one of them fails. How would I retry that? I really just focus on the one API request I make. The trade-off with the back and front end is that you have this new piece of infrastructure that you need to maintain and again more latency because we have an extra request. But in general, if you use graphql, you can parallelize a lot of the request. You can do a lot of caching and it gives your client team so much flexibility. Now, as you start adding all those microservices and back and frontends and API gateway, you still need a way to figure out if bugs happen. And right now failure it's distributed
because in a microser architecture you usually get a request to payments then it goes to analytics then maybe it has to call o to get some permission and then maybe it sends an email. If any of them fails you need to work your way back and send a message to the user. It's also much harder to debug. I remember working on a microser architecture and we had 11 downstream services. So imagine if there's a bug in service number 10. you need to work your way to all these different logs of all these different services and that is extremely hard if those logs are in different places. So what we usually do is we add a distributed logging platform
and tracing and we have a correlation ID that means whenever request comes in we have this unique ID that gets attached to all these downstream microservices and back in our platform we can actually trace that down to all the different spans. Whenever you have one of those requests that involves so many calls, this is our big request, but we split it in four. That's a oversimplification, but you definitely need a service like this one. And you can use parameter and graphana, which are open source or data dog, but that's a requirement for microservices. You need to implement distributed logging and tracing. And finally, we can apply the same mentality
to our front end and split our front end in independent front ends. Remember, we had the modular front end. We can take that a step further and have our front end divided into individual independent applications that then are stitched together by a shell. And the shell is the one that takes care of all the shared state like authentication, the team, maybe the different language settings and all the shared state. Now to make sure that these applications that are deployed individually and they are developed independently look the same and behave more or less the same and we share the same UX patterns and we share the same CSS. We usually have a shared design system and so going back to what I mentioned about AI as AI is writing all the code as an engineer you'll either be doing platform work for example if you're a design engineer you're building the design system or
you're a product engineer that builds features with all these building blocks. So you usually extend the BFF, you work a lot on the micro front ends and maybe a bit on microservices or you're a system engineer that's more of owning the back end. Now the reality of having microservices and microrends, it's a bit more messy. As you see, I added here thousands of requests, but it's extremely easy to get lost here. The other problem is as we add more microservices and more microservices, it just becomes impossible to know what really happens in the system. The BFF might request the product microser. But
first, it will need authorization from O. We need to call the analytics to log the fact that there was a call. Probably for the products, I also want to know if they are in stock. So I call the inventory microser and so on so forth. It gets extremely complicated to implement logic because most transactions are distributed. That means to read something or to write something I need to talk to five or 10 microservices. And the solution to that is to move into an event-driven architecture. And in an event-driven architecture, we have producers and we have consumers. And so basically, the producers will produce events like the
payments microser might trigger an event that's called payment creator. And so another micros service might be listening to this topic of payments and decide to do something with it. A good example is analytics. So the analytics will actually store the fact that somebody's trying to create a payment. When the payment has succeeded, probably the analytics again will thank that payment has succeeded. And that's an easy way for marketers, for example, to know how many payments are successful. And the same goes with pretty much any other service. Most services will be both producers and consumers at the same
time. So they can produce events that go into an event broker, but also consume. And the advantage here is that it's extremely easy to add a new micros service to the mix. Let's imagine I want to add an antifra service right here. The only thing I need to do is to plug it in here and then listen to the payment skew. And the best thing is that if you use services like Kafka, they store all the events you've done from the beginning of the history. So when I add this microser of fraud, I could actually run it on everything that happened since the beginning of times. Now what is the trade-off of the event-driven architecture? The problem
with event- driven architectures is that everything becomes eventually consistent. That means all of the sudden everything takes a bit longer because things have to go to the event queue. They have to be processed in order. And even if those components are very performant, it's just going to take time for changes to propagate in the system. However, it's pretty much the only way to scale beyond a certain point. The big advantage of event architecture is that it's a lot easier for a much much bigger team to work on the same software system because they can work in isolation. The only thing they need to care about is the API they expose, the events they
have to react to, and the events they need to publish. They can also release features asynchronously and they just need to declare which events they are pushing into the event broker and the other teams are just a way to see okay do we need any of that does it make sense for us to listen to their topic or not. Now probably the most important thing in an event architecture it's the structure of the events. Events are usually statically typed and they also contain a token because some of them might need authentication and that's how you make sure that services really access only the events that they are supposed to touch. And to illustrate a
bit more how it works, we might have an orders micros service that creates an order created event and that gets pushed to our event broker topic and listening to that event there's different microservices. Those microservices will process the event and then maybe they'll produce their own events. So in this case when an order is created the inventory microser listens to it and starts preparing the order. The email microser might send an email and then it might even create an event that the email was sent and the analytics microser it's listening to all of them. Event architectures are one of my
favorites but they do add certain restrictions and you also need to provide a lot more realtime feedback to your users. So you might use realtime APIs like polling or service events to let your users know that something happened in the back end. And that kind of logic it's usually what you implement in our BFF. If you use GraphQL, you can use GraphQL subscriptions for real time. And if you have a plain REST API, you can either do polling or websockets. As I mentioned, I'm planning a longer video on API design and real-time communication. So, make sure you subscribe to get it as soon as it comes out. By the way, if you're a software
engineer and you're looking for the way to get to next level, to become more senior, to maybe get a better job or just a job where you have more responsibility and you enjoy more, make sure you check out the link to our software master program in the description. We already worked with over 450 engineers and got them to the senior level or beyond as I mentioned. Link in the comments. Now, let's talk about our next architecture style, which is the hexagonal architecture. But before we do that, let's go back to our layered architecture. So the problem with our MBC and our layered architecture in
general is that the dependency boundary is not really clear which means that the view will depend on a controller but then the controller will depend on the model and the model depends on the database. So in this layer architecture we have what we call tight coupling. That means all these things are kind of stitched together and whenever we change something in a controller we might have to make a couple of changes in our models or in the view and especially in the model for example if we change something in the model if we change interface it exposes it will change everything downstream. So the dependencies are not super clear. It
looks layered but in reality there's a lot of calls from one module to the other and the hexagonal architecture it's a response to that. Basically the idea of hexagonal architecture is that our dependency arrow will only look inward and what it's at the core of architecture that's basically our domain that's a highle entities let's imagine in the comments case it could be payments or user and those are high level they're not data tables you can think of them as typescript types it just says what the user is and what are the different fields and what are the different relationships now the application itself the transactions you need to do they depend on that domain. So you might have things like place an order and that's [clears throat] a function and it uses all the entities to place an order. So it might call the order repository and do the logic and needs but it only depends on the
internal layer. That's the domain. And in the outer zone we have what we call the adapters which can be driven or driving. So the driving adapters are basically our UI, our BFF or even incoming events. And the three adapters are our database or the events that we need to create. To put it simply, you can use hexagonal architecture to better architect your microservices. So inside the microser itself, you'll have a set of domain entities. And the cool part about domain entities is that they are the core of what happens at the business level. They are the most abstract representation of the problem you're trying to solve of your domain space. And so they are very unlikely to change. Everything else that has to do with the infrastructure on the application itself might change. For example, you could add a new controller that would be a GraphQL
controller. But given that the dependency it's outward, you'll have to make no changes to your application because in reality, you're just adding a new API. But your domain did not change. The problem that your software is solving didn't change at all. So why should code change? And that's the main idea behind hexagonal architecture. Now building on the idea of external architecture, there's also the onion architecture and finally the clean architecture. Now all those have in common this idea of the dependency chain and they structure dependencies in the same way where the core of application is the entities. They're independent. They don't depend on any layer and they will not change. So it doesn't really matter for example if we would store this in a relational database or in a non-reational database. Those things at the core will never change. We only
change the outer adapters. Now the hexagonal architecture, onion architecture and clean architecture are exploring the same idea and there are ways to structure a backend microser or a backend monolith. But when it comes to real practice, if your service is split in independent microservices, you won't necessarily need to go this far. Most of the times a simple layered architecture is good enough. Before we move on to the AI side of things, I wanted to mention CQRS. You probably heard about this depending on the framework you work with which is a command query responsible disgregation. At a high level this means you split your reads from your rights. So even if for the client it looks like they call the API gateway and they make a post to create some data and a get to get data when it comes to how you handle this you have a very different path. So
the reads are written by what we call the command side that's going to write that in a posgress database and then probably trigger the event that a word was created and finally and we can even use what we call a projector to store that for faster reads. So as you see this is our write operation. Now whenever a user needs to read an order they could just read it from our elastic search layer. This is really useful when you want to read something in a specific way or you have a lot of reads and you want to scale them really well. So what you do here is that you trade a bit of consistency because this system will only be eventually consistent. That means the latest read will only be equal to the latest write after a couple of seconds. And so this layer of reads allows us to read our data in different
ways but also to read it in a very scalable way. And so whenever your reads are very different from your rights, you might consider this. But again, it adds more complexity. So in general, you want to keep things as simple as possible before we get there. Now with AI, we have some new ideas when it comes to software architecture. And I want to mention them really quickly. The first one is the Zo token architecture. So what is the problem that the Zotoken architecture solves? Imagine we want an LLM to classify our orders. So from our order service, we receive an order created and we kind of want to classify
that. The first temptation is to send that to something like the Entropic API or the OpenAI API and give it some types and then get back what is it. But the problem here is number one that will generate a lot of cost. Number two, it might be very slow because calls to an LLM tend to be quite slow especially if you're using a reasoning model. And finally, if you retry an event, you're paying twice. So how can we solve this in a more token efficient way? That's where the idea of Zo token comes in and to explain it very easily rather than having the L&M solving for example a classification problem. What the L&M does is to build a set of rules. Those are static rules that we run on our events. Those are the ones that run in production. They are deterministic and they are much faster than passing the data to an LLM. Now whenever there's an exception where we receive something and
we don't know how to classify it with our deterministic system, we can push that to the LLM and then based on the LLM result, we update the rules. So the LLM here is just solving the edge cases. It does the thinking and it builds the rules. But the production system itself, it's using as much as deterministic code as possible because it's more predictable and it's a lot cheaper. And the next AI or agendic architecture I want to talk about is the one of a agent and in this case I broke down cloud code which you probably used it at a very high level. And so what is an agent? We can think of an agent as a while loop. Basically it will receive a prompt and it will try to derive a condition and it will keep calling the model until it fulfills that condition. Now the agent has memory files, the cloud MD, the
memory MD, but it also has some session information like the tools you added, your skills, your MCP. And what it will do with all this is to build the context window, which is what it sends to the API all the time. It will send that to the model. The model will do inference and try to generate a response. Now inside that response, you might have what we call tool calls. And this is an example of how that response will look like. For example, here it says, hey, we need to use the lookup weather tool. And this lookup weather, it's a tool that was defined. So, it has a functions.lookup weather. So, all this goes back into the agent, back into the harness. It gets parsed and then the harness will end up calling the weather tool and passing the results back into the context window here and then calling the model again. And it keeps doing this until it fulfilled the goal it had. Now,
this was a very highle explanation. If you want me to make a video specifically about agent architecture, let me know in the comments. And the final architecture that I see people throwing around, it's how do you manage architectures where you have several agents and the most popular one is the supervisor model. So in this case, you have a general agent that is basically the supervisor agent but can then invoke different other agents depending on the task at hand. So to basically try to classify the task at hand and see if it should use a specialized agent. The specialized agent has less context, but it has access to
specific tools. And the reason why you would do something like this is because if you add all those specific tools and this context into the main agent, the performance of this agent will be lower and lower. So, it's a way for you to split context. Let's imagine the research agent starts pulling in a lot of different websites and a lot of text to try and reach a conclusion. It's much more effective if what we pass back to our supervisor agent is just the final result rather than the whole process. If we were to pass in the context of each single sub agent into the supervisor agent, we'll go beyond the context window and the results we'll get will be pretty poor. Keep in mind that this whole AI architecture is still at the very beginning and the industry is still defining the best practices. But those are the three most consolidated ideas as of now. Finally, I wanted to mention the
micro kernel architecture. And this is the one systems like Linux use. We have a main core. That's pretty much the essentials of the program. And then we can extend that by installing plugins. And plugins will hook up in the different life cycle hooks that the core exposes. So a plugin might add something in the UI. It might add a peripheral. It might modify a specific workflow depending on what the core exposes. Now this kind of architecture is very useful if you're building applications that someone can install and if you want to leave them open so somebody can extend on them with a plug-in. Very good examples of the microchanual architectures are VS code and cloud code. They both have plugins and whenever you add a plugin it will connect to different hooks and add its
own logic extending what the core the kernel can already do. And with that being said we are done. So, we just saw every single type of software architecture out there.