August 09, 2018 The Role of Layers in Web Applications Dive into a high-level overview of web APIs, discover why multilayer architecture is a good thing, and learn how to split an application into layers. Dimitar Zhelev var introduction = new Introduction(); When I first started coding with teams of other developers, one of my biggest problems was finding the right place to put my code. I’ve been involved in projects where developers didn’t have a plan of how to structure their application, which often led to coupling between modules, bugs, performance issues and little to no scalability. I would feel exhausted by the time I finally became familiar with the project. Even though I had a general understanding what it should do I still had no idea what caused a bug and if I was going to break something else by fixing it. However, I have also worked on projects where I didn’t even need to familiarize myself with the code base – everything was so logical it came naturally. While this article is meant for new developers who may not know how to structure their web applications, it will also be useful for experienced ones who may know patterns and best practices but haven’t fully structured their knowledge. It may also provide an alternative to what you have considered carved in stone. Learning how to think in modules has helped me a great deal in my growth as a developer. I will be talking mostly about conventions and I am not going to preach “the way.” I am going to present an alternative to people that are not familiar with these approaches. What Are Web APIs? Let’s start with a few words about Web APIs. A Web API is a program that intercepts a specific http request, performs some business logic, and then returns a result. Simple, right? Since a single web API can intercept many different requests and perform many different operations (therefore grow in size), developers tend to organize the code into different layers, where each layer has its own set of responsibilities. Segmenting the application into different layers has a huge benefit—you know where to look when a problem arises. This might sound boring but in software development this is the equivalent of a superpower. I think of it as “you will get a sixth sense of where the problem is.” This makes a huge difference when you have to fix a bug, introduce a new developer to the project, build on top of an already existing feature or any other scenario that involves reading code. In his book “Clean Code”, Robert C. Martin found that the ratio of time spent reading versus writing code is well over 10 to 1. This illustrates just how important writing readable code is. Additionally, this approach greatly benefits scalability by decreasing the coupling in your code. Decoupling plays a huge role in scalability since you want your code to affect the fewest possible places. The fewer places your code affects, the smaller the chance is that you break something else. How I Prefer to Divide My Application Layers This is probably not the first time you’ve read about multilayer architecture, and while this is something that “everybody knows” I often find people neglecting it. I divide my applications into three major layers: Controllers Services Repositories Let’s get into a little more detail so we can explain what each of these layers stands for and what its responsibilities are. Controllers First we need an entry point to the Web API. An entry point is a method of a class which intercepts a specific request. As a best practice I find that entry points that have the same context should be placed inside of a class that unites them – mostly known as a Controller. I.e. http://localhost/user/create and http://localhost/user/{userid}/update are two requests that are both handled inside of the UserController class as two separate methods – Create and Update, because they both refer to the “User” entity. At a later point of my application I will handle http://localhost/account/create and http://localhost/account/{userid}/update into my AccountController class since are both operations performed on the “Account” entity. Controller Responsibilities: HTTP – Having the ability to intercept requests means that you need to be aware of what HTTP is. Usually in my applications Controllers are the only part of the application that is HTTP-aware. Model Binding – Parsing the passed values (i.e. body or query parameters) into a model. In .NET this is usually handled automatically by the Model Binder. Action Authorization – There are two types of authorizations – action and resource authorizations. Action permissions answer the question, “Is this user allowed to view reports in general?” While resource permissions answer the question “Is this user allowed to view this specific resource?” Usually action permissions are implemented with roles or an array of permissions. Since Roles (or permissions) are usually passed as claims this makes Controllers the best place to handle them via Attributes for example. Response – We’ve defined Controllers as an “entry point” to our application, but so far we haven’t said that they are supposed to be the “exit point” of the application as well. Since only Controllers are aware of HTTP, it is their responsibility to also create the HTTP response. This may include exception handling as well – I.e. you might change the response based on the exception that has been thrown. DON’T implement business logic inside of a Controller – business logic is a huge part of the code base of a project. Mixing it with what we have already listed above can become overwhelming and hard to follow. That’s why we move the entire business logic inside of a separate layer called “Services” and let Controllers reference them. DON’T write database queries inside of a Controller – this is implied in the previous bullet but I wanted to stress it here as well. In this approach we are actually going to create a separate layer for those as well called “Repositories.” Services Each business action is implemented as a method in a class. As a best practice I find that methods that have the same context should be placed inside of a class that unites them – mostly known as a Service. (i.e. UserService, AccountService and EmailService). As I’ve mentioned before, business logic is a huge part of the code base of a project. Depending on your specific business logic some services will become bigger than others and some might even seem out of control. Don’t be afraid to break down a service into smaller ones. For example – User service might potentially be broken into: UserService – For general use UserSettingsService – Related mostly to the user’s personal preferences UserFavoriteService – Because Favorites got out of hand Service Responsibilities: Business Logic – Basically whatever the business needs to accomplish happens here, whether that is assigning permissions, calculating standard deviation, or sending an email. Model State – Most people handle the state of a model inside of the Controller. But there are some things I don’t like about this approach. Services may call to other services in order to complete a task. If we validate the model inside of the controller, that would mean that we would also have to implement the very same validation everywhere else we refer to this method. Handling the model state inside of the service ensures that it would be safe to call any method wherever you are in the application and not to worry about validation. So why don’t we write the validation inside of the model? Validations are often more complex than what would be acceptable inside of an attribute of the model. So what developers often do is write the simple ones (MaxLength, MinLength, Range and so on) inside of the model and the more complex ones inside of the Service/Controller. This makes it more difficult to find problems since you would have to look at two places to verify the validation.Personally I’ve found that it works best to write all of the validation inside of the Service, but don’t be afraid to experiment and get the best feel for yourself. Resource Authorization – Unlike action permissions, resource permissions are stored inside of the database or are determined by some complex logic. Both of those scenarios make Services the best place to handle it. DON’T write database queries inside of a service – Generally “how” are we going to extract information from the database or “how” are we going to add a new record is irrelevant for the business logic. Since these queries are again a significant part of our code base we will need a separate layer to handle those – “Repositories.” We will discuss repositories in more detail in a future blog post. DON’T build HTTP responses – We’ve defined services as a place where we implement the business logic of the application. Services are supposed to be able to reference themselves which makes it best not to couple them with HTTP contexts. Leave this for Controllers. Repositories Assuming that you’re writing an application that needs to persist data (meaning that data survives after the process that created it has ended) then you will need a different layer to handle this sort of logic. This is known as a Data Access Layer (DAL). Each database query is represented by a method inside of a class. Just like the previous layers where we grouped similar methods into the same class named a Repository (I.e. UserRepository and AccountRepository). Some may ask why would they need an additional layer just for this. After all, isn’t data persistence part of the “logic?” This simply makes the code easier to read. The method you use to persist data (writing to file, DB, etc.) has nothing to do with the business logic that we are executing and so it would only get in the way. For example, imagine that you’re trying to explain to someone what a washing machine is. You probably wouldn’t say something like: “It’s a device powered by a form of energy resulting from the existence of charged particles (such as electrons or protons), either statically as an accumulation of charge or dynamically as a current, that washes pants using a colourless, transparent, odourless, liquid which forms the seas, lakes, rivers, and rain and is the basis of the fluids of living organisms.” Instead, you would say: “It’s a device powered by electricity that washes pants using water.” Everyone knows what electricity and water are, if they don’t they can read all about it elsewhere, since it’s a “different story.” Now back to Repositories. They are an abstraction of database queries. Their purpose is to reduce complexity, improve code reusability and make the rest of the code persistence ignorant. As a bonus, the repository pattern allows you to write unit tests instead of integration tests. This means that you can test your business logic (the Service layer) by mocking the Repository layer. In his book “The Clean Coder: A Code of Conduct for Professional Programmers” Robert Martin states: “Unit tests are documents. They describe the lowest-level design of the system.” On top of that, without unit tests how can you actually make the statement that your application is actually working? But I’ve gotten a bit carried away. Unit tests are not the focus of this conversation. Repository Responsibilities: Always return materialized data (object in memory.) This removes any possibility that multiple requests are made to the database when referencing a property, looping, mapping, etc. This kind of mistake causes huge performance impacts and is one of those problems that has excellent local performance (since you don’t have a lot of test data and no significant traffic to your local database,) However, on production it takes forever. If you expose both materialized and non-materialized data from the repository layer sooner or later someone is going to make the wrong assumption and you’ll have problems like above. Always extract only what you need from the database. This optimizes the network traffic. The most common pitfall I’ve seen is when people materialize the whole “User” entity instead getting only the properties that they require. Always populate your entire model. When you are projecting a database query result into a model, always populate all of the model’s properties. This way you would never have to awkwardly ask yourself, “Is this property null because its value in the database is null, or have I simply neglected to map it?” This makes all the difference and eliminates guesswork during development, which is always a win. Following those three, using a type safe language like C# can become harder than expected. I will write a future post about the interaction between Services and Repositories that demonstrates an easy way of incorporating all three of those rules at the same time. Never perform business logic inside of a repository method – This is a good place to note that there is a difference between “filtering” and “business logic.” It’s a very thin line, but having something inside of your “where” clause is ok because this corresponds to one of the core responsibilities of the Repository layer (extract only what you need). On the other hand, having an “if” statement or a “foreach” loop is not ok because Repository methods serve as an abstraction of a database query. Performing in memory filtering inside of a foreach loop has nothing to do with your database – This is more of a service specific thing. Never reference a service – This is mostly explained in the previous bullet, but I want to stress it again here. This will also help you avoid circular references (a service has a dependency to a repository and the repository having a dependency to the service making both unresolvable.) Avoid referencing other repositories – This is more of a “try to keep as little references as possible.” You might end up with a circular reference (repository1 having a dependency to repository2 and repository2 having a dependency to repository1 making them unresolvable). var summary1 = new Summary(); Multilayer architecture helps a great deal when it comes to scalability, bug fixing, introducing new team members and so on. How to breakdown your application into layers is entirely up to you. What I have outlined here is just an approach that works very well for me. It’s understandable if you’re having trouble remembering each point, as there are a lot of bullets. To identify what should go where I would suggest simply asking yourself the following: Do I really need to introduce a dependency between these two modules? Can I keep the entire X responsibility into a single layer/module? var summary2 = new Summary(); summary2.RequiredState = RequiredStates.Optional; While I have outlined some “best practices,” keep in mind that there is never an “ultimate best practice.” In fact “best practices” should be renamed “nice things to consider in the following scenario” because people forget how situational they can be. The main purpose of this post was to provide a basic overview on what web API is, why multilayer architecture is a good thing, and how to actually break your application into pieces. But at the same time I didn’t want to just make statements like “this is good” and “this is bad,” since nothing, or almost nothing, is just black or white. Nowadays there’s a best practice for everything and it’s difficult to define where they apply, not to mention what would be a good combination of best practices “in general.” That is why I wanted to provide reasoning on how I reached my conclusions. Scrolling back leaves me with a sense of anxiety since I want to say so much more about every section, but at the same time I know it’s not the right place – just like the main struggle in multilayer architectures. Tags DevelopmentWebSystems Architecture Share Share on Facebook Share on LinkedIn Share on Twitter Over-Engineering Learn the causes and costs of over-engineering your solution and how to avoid it from occurring in the first place. Download Share Share on Facebook Share on LinkedIn Share on Twitter Sign up for our monthly newsletter. Sign up for our monthly newsletter.