September 13, 2018 Service and Repository Layer Interaction in C# In part two of our series on Web APIs, Senior .NET Developer Dimitar Zhelev explains how service and repository layers interact in C#. Dimitar Zhelev (This is part two of our series about Web APIs and how to properly apply a multilayer architecture to them. If you missed part one, you can catch up here.) In part one of this series, we discussed to how to properly apply a multilayer architecture to a Web API. You may recall we spent a good chunk of time discussing repositories. To recap, repositories: Always return materialized data Always extract only what is needed from the database Always populate entire models This might prove to be a bit of a challenge, however, if you’re using a type-safe language like, say, C#. Extracting only what we need from the database would mean that we would have many different projections (or selectors). Populating our entire models would mean that we would have a model for each projection. This would result in many different repository methods for something that looks like executing the same query over and over again. In the previous chapter we said that a repository method should be an abstraction of a database query and at this point, it feels like we are writing the same query over and over again just so we can match the object types. Like they say in the cheesy commercials – “Fortunately you have come to the right place!”. Today, we’ll focus mostly on service and repository layer interaction. I’ll start with an example and gradually improve it so it can become clear what the problem is that we’re solving. As an example let’s extract information about a specific user by ID. In order to do this, our sample repository method would look like this: public async Task GetUserBasicInfoDTO(int userId) { var result = await _context.Users .Where(x => x.Id == userId) .Select(x => new UserBasicInfoDTO() { FirstName = x.FirstName, LastName = x.LastName }) .SingleOrDefaultAsync(); return result; } The UserBasicInfoDTO would look something like this: public class UserBasicInfoDTO { public string FirstName { get; set; } public string LastName { get; set; } } So far so good. But we also have to extract the user details. This demands a model with a few more properties in addition to FirstName and LastName. Some might say that we would just add the properties into the UserBasicInfoDTO model. But that would mean we’d have models that don’t have all of their properties populated. This makes things confusing and generally leads to more questions than answers. On the other hand, if we populate them in GetUserBasicInfoDTO() then we would have extracted data from the database which we simply would not use, which is yet another violation. The primary issue with extracting data that we don’t use is the network traffic and, again, confusion. It’s easier to read a model that has five properties than a model that has fifty. A type-safe language like C# can make our job rather hard in such situations. Why? Because a method in a type-safe language should return data in the form of an object from a specific class. This requires us to declare the class, which means writing more code. If you are not using a type-safe language, you can return whatever object you wish without the need of a specific type and its properties. That’s one of the downsides of having a type-safe language. On the other hand, type-safe provides a lot more transparency and C# is a great language. But, after all the solution is in right front of us. We need to create a new model with a new repository method: public class UserDetailsDTO { public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } public string Gender { get; set; } public string AvatarUrl { get; set; } } Its corresponding repository method will be: public async Task<UserDetailsDTO> GetUserDetailsDTO(int userId) { var result = await _context.Users .Where(x => x.Id == userId) .Select(x => new UserDetailsDTO() { FirstName = x.FirstName, LastName = x.LastName, Age = x.Age, Gender = x.Gender, AvatarUrl = x.AvatarUrl }) .SingleOrDefaultAsync(); return result; } When you think about it, another model makes a lot of sense. Both models serve different needs of the application. While they might look similar, their purpose is different. Tomorrow the developer might have to add or remove a property to or from the model. Since they’re both separate models we shouldn’t have any doubts if we’re at risk of breaking something. But what about the repository? A repository method is “an abstraction of database queries,” but it feels like we’ve performed the same query over and over again and just changed the output (the projection). Sure, the projection is still a part of the query but I just can’t shake the feeling that we can do better. What if we define that the projection is part of the business logic and not the query itself? Let’s look at how that would happen. As you can see, the Select statement takes a parameter, which is simply an Expression of a Function. We have a source for the entity we’re performing the operation on (in our case User) and a result that is the type that we are projecting (in our case UserBasicInfoDTO). Let’s extract it as a parameter and pass it to the method: public async Task<UserBasicInfoDTO> GetUserBasicInfoDTO(int userId, Expression<Func<User, UserBasicInfoDTO>> selector) { var result = await _context.Users .Where(x => x.Id == userId) .Select(selector) .SingleOrDefaultAsync(); return result; } We would still need two repository methods. What happens if we make the method generic? public async Task<TResult> Get<TResult>(int userId, Expression<Func<User, TResult>> selector) { var result = await _context.Users .Where(x => x.Id == userId) .Select(selector) .SingleOrDefaultAsync(); return result; } Much better. I’ve also renamed the method to “Get” because we know it is “by userId” since we require it as a parameter. Also, we don’t need to specify what type we are returning, because it can be ANYTHING! Inside of the service, the two calls would look like: public async Task<UserBasicInfoDTO> GetUserBasicInfo(int userId) { var userInfo = await _userRepository.Get( userId, x => new UserBasicInfoDTO() { FirstName = x.FirstName, LastName = x.LastName }); return userInfo; } public async Task<UserDetailsDTO> GetUserDetails(int userId) { var userDetails = await _userRepository.Get( userId, x => new UserDetailsDTO() { FirstName = x.FirstName, LastName = x.LastName, Age = x.Age, Gender = x.Gender, AvatarUrl = x.AvatarUrl }); return userDetails; } This accomplishes what we set out to do. We have: Returned materialized data Extracted only what we need from the database Populated our entire models But now we’ve introduced a few new problems: The projection takes up a ton of space and this is a rather simple one. We could have way bigger selectors. On top of that, we don’t really need to see it. After all, our convention states that every property of the UserDetailsDTO model will be populated and we’ve picked it because the model is going to satisfy our needs and we’re going to use its properties. What if we want to use UserDetailsDTO in another service method? Am I supposed to define the projection each time? Clearly defining the projection as part of the business logic doesn’t seem right. After all, we’re always going to populate every property of the model which implies it should be a part of the model. Let’s see how it looks: public class UserBasicInfoDTO { public string FirstName { get; set; } public string LastName { get; set; } public static Expression<Func<Entities.User, UserBasicInfoDTO>> UserSelector { get { return user => new UserBasicInfoDTO() { FirstName = user.FirstName, LastName = user.LastName, }; } } } public class UserDetailsDTO { public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } public string Gender { get; set; } public string AvatarUrl { get; set; } public static Expression<Func<Entities.User, UserDetailsDTO>> UserSelector { get { return user => new UserDetailsDTO() { FirstName = x.FirstName, LastName = x.LastName, Age = x.Age, Gender = x.Gender, AvatarUrl = x.AvatarUrl }; } } } This way the call inside of the service would look like this: public async Task<UserBasicInfoDTO> GetUserBasicInfo(int userId) { var userInfo = await _userRepository.Get(userId, UserBasicInfoDTO.UserSelector); return userInfo; } public async Task<UserDetailsDTO> GetUserDetails(int userId) { var userDetails = await _userRepository.Get(userId, UserDetailsDTO.UserSelector); return userDetails; } As you can see, I’ve defined the selector as a static property of the model class. A model can have multiple selectors, which might be useful if we are making a projection from different sources. As a result, the service methods are really simple to read and state clear intentions. I need this model because of these properties. Scaling Since this was an introduction to the approach, the example that we used was rather simple. There are two main ways this approach scales: Query — refers mostly to the filtering and navigation sections, excluding the projection Projection — refers only to the mapping part Query Scaling This approach scales flawlessly in terms of query. This is not reflected very well in the sample, but imagine if we have soft delete for users. Then each clause would look like: .Where(x => x.Id == userId && !x.IsDeleted) This can be rather hard to maintain when it occurs in many places. Since we are not going to state the “projection” as part of the method name we can give hints about the query itself. For example: EventReository.GetNextWeek() RaceTrackRepository.GetMostPopular() Projection Scaling While I find this approach to scale very well in terms of projection, there are times where the projection can be a hundred times more complicated than the query itself. Although there’s nothing wrong with placing a huge projection inside of the model, it makes more sense to have it inside the repository. In my personal experience, I’ve only had to consider this once but I can imagine people might be dealing with such issues more often. Breaking the Convention This is all just one convention. As we know there are exceptions that sometimes require us to break a convention (just as I’ve mentioned above with extremely complicated projections). A great benefit is that this approach is completely transparent. If someone decided to break the convention, then there wouldn’t be an extra parameter inside of the repository method. You could see if there was a specific reason why the person decided not to apply the approach or he was simply sloppy. Arrow Functions/Lambda Expressions Often when we perform an update operation we need to extract the entire entity from the database, not just part of it. Other times we need just one property and it would a bit of a waste of time to create an entire model out of it. This is the time where we should remember that the selector is simply an arrow function (or lambda expression if you prefer). This allows us to do some really neat calls to a repository method, such as: Select the whole entity User user = await _userRepository.Get(userId, x => x); *Of course, you can always expose it as an overload if you want to skip the expression. Select just a portion of it int id = await _userRepository.Get(userId, x => x.Id); sting name = await _userRepository.Get(userId, x => x.Name); Integration with Generic Repository Pattern I won’t fully dive into generic repository pattern here but if you’re not familiar, you can learn more about it here. Integrating this approach with the generic methods inside of the “BaseRepository” class can provide an out-of-the-box implementation for every entity no matter what the implementation of your BaseRepository is. For example: public async Task<TResult> GetById<TResult>(int id, Expression<Func<TEntity, TResult>> selector) { return await this.GetById(id) .Select(selector) .SingleOrDefaultAsync(); Or public async Task<List<TResult>> GetAll<TResult>( Expression<Func<TEntity, TResult>> selector) { return await _context.Set<TEntity>() .Select(selector) .ToListAsync(); } Coupling You might read all of this and wonder, “aren’t we just coupling the Models layer with the Data Layer?” Yes, we are. All of the expressions have to be compatible with LinqToSql syntax. To those who still don’t see what we’re talking about, here’s another example. Imagine that your project is using two different databases. One that is based on SQL Server and therefore uses LinqToSql in its repositories and one that’s not. Both repositories are consumed by the same Service layer and are using the same Models. This might leave you with a selector that would work for one of the repositories but not for the other ones. This is a great indicator that this isn’t a “best practice” placement of the selectors. So, now we ask: What would be the “best practice”? Why did I choose to demonstrate this version of the solution? There are two possible solutions that come to my mind: Create a separate mapping layer between the services and each of the repositories layers. This will not remove the coupling issue but it would give it enough context to justify the coupling since each mapping layer would be responsible for its own repository. Move the mappings inside of the repository layer. This can be achieved by passing the model (instead of the specific selector) to the repository method and have the definition of the selector defined inside of the repository layer. For example: public async Task<TResult> Get<TResult>(int userId) { var result = await _context.Users .Where(x => x.Id == userId) .Select(StaticMappings.For<TResult>()) .SingleOrDefaultAsync(); return result; } Then why would I choose the first approach for this article? The answer is simple: simplicity. Placing a static property inside of the already existing model layer is fast, easy, and explicit (which makes it understandable). The other approach is considered more generic but, to be honest, I haven’t encountered a scenario with more than one different database sources. Moving the projections to the repository layer would only solve a problem which I don’t have. Summary By following the simple guidelines from our first article, we not only found a good solution but also managed to push the level of abstraction of the repository layer. This improved the developer experience while keeping everything explicit and transparent. Personally, I love this approach and use it in every project that I have worked on so far. While it solves the previously mentioned problems, it also saves me so much time from generating redundant methods. Thanks to the Base Repository pattern I have so much functionality out of the box by inheriting from the base class. But don’t take my word for it. Take it for a spin yourself and don’t forget to share your thoughts on our facebook page. Image Source: Taylor Kiser on Unsplash 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.