3

This is my first time I am using repository pattern and applying a layered architecture in a project. I have followed the article found here. The complete code found on the article can also be found on the writer's github.

I read the whole article beginning to end before getting my hands on the keyboard to start coding. It al made sense (still does).

I am not sure if I have to summarise the whole article here but in simple terms, he divided his solution into 4 projects. Core, Data, Services and WebApi. Repository Pattern and UnitOfWork is used to abstract data access.

After reading the article I decided to apply the very same logic to my project. After doing so, I realised it took me the whole day and I found myself writing a lot and a lot of code which seemed unnecessary and duplicated. For example, in this article he only have two entities. Hence it seemed like a very simple application. I currently have 28 and that's only the beginning! This ment, apart from my actual model, I ended up having 28 separate repository interfaces, 28 separate service interfaces, 28 actual repositories, 28 actual service classes and similar number of DTOs (Resource classes) for mapping purposes.

So. I am happy to do the extra work if it will prove to be a solid foundation for my project and if it will contribute positively to my learning progress. However, something inside tells me I've done a lot of unnecessary work, which either could be simplified or probably have a better way of doing. Hence, I am here seeking advice if this article is guiding me in the right direction.

I am using the latest versions (including pre-release) of DotNet Core and Entity Framework Core.

7
  • You don't need 28 repositories, it's not supposed to be a repo per entity. Probably 3-5 or so will do. Organize the repos (and your objects) around your use-cases, so that for a use case a repo can return the objects you need together (in one or in a few method calls). E.g., his MusicRepository.GetAllWithArtistAsync() returns Music objects and the associated Artist objects. The top-level objects can, but don't have to be EF entities. Commented Oct 29, 2020 at 21:26
  • No matter what architectural style you use, an application 28 entities is going to have a lot more code than one with 2 entities. I personally don't use the generic repository pattern and unit of work pattern that the author uses, but his approach is pretty solid in general and pretty common in the industry.
    – Eric King
    Commented Oct 30, 2020 at 3:09
  • @EricKing thanks for the comment. I've come accross to one of your answers to a similar question and the accepted answer here. However it has been 8 years since that. Could you let me know if your answer is still valid for that question? If not could you answer the very same question in today's time? Commented Oct 30, 2020 at 6:25
  • @FilipMilovanović thanks for the comment. So how can I implement this idea with a generic service? (if it is possible of course) He also registers services in the Startup.cs As you said, for most of the entities the generic repository is more than enough form me as the need for anything other than the basic CRUD will not be needed for ever. But I am quite new to the concept to figure out how to implement it to this code. Any ideas? Commented Oct 30, 2020 at 6:57
  • Since you mentioned EF and Repository/UoW, you might want to read this answer: Why shouldn't I use the repository pattern with Entity Framework?
    – Rik D
    Commented Oct 30, 2020 at 7:50

1 Answer 1

7

he divided his solution into 4 projects. Core, Data, Services and WebApi

Just as a hint for finding more documentation on the subject: Core, Data, Services sounds a lot like the Domain, Persistence (or Infrastructure) and Application layers that Clean Architecture promotes.

You can find many articles on the subject. Here's my favorite resource, but many more exist.

After reading the article I decided to apply the very same logic to my project. After doing so, I realised it took me the whole day and I found myself writing a lot and a lot of code.

If having to spend a day writing code is a problem for you, then you're damned if you do and damned if you don't. There's one thing that's very important to remember:

Clean coding takes upfront time and effort, but will save you more time and effort in the long run.

It may suck now, but if you don't do it, maintaining your codebase is going to suck even more in the future. I've seen many projects go into absolute development hell because good practice was thrown out of the window in favor of quick initial results.

It's not that uncommon for some projects to even collapse in on themselves and never being completed, when the bugs are incessant and the technical debt accrues more and more because no one can be bothered to now invest even more effort to clean the dirty code (compared to having kept it clean from the beginning).

which seemed unnecessary and duplicated

"Duplicated" is ambiguous here.

If you mean to say that within a layer there is a lot of copy/pasted code, then you should evaluate your code for reusable features and try to abstract them.
For example, maybe your repositories have some reusable logic (e.g. getting an entity by their ID) that can be abstracted into a base repository.

If you mean to say that your layers all have the same general structure to them; then you're missing the part where each layer has its own distinct role to play:

  • The domain layer (Core) houses your business logic
  • The persistence/infrastructure layer (Data) handles your database interaction
  • The web layer (WebApi) handles the incoming and outgoing web requests
  • The application layer (Services) connects all these layers

Initially, your codebase will be an empty shell, and all these empty shells will look the same. But as you start filling them in, you'll notice that each shell gets filled in differently.

For example, in this article he only have two entities. Hence it seemed like a very simple application. I currently have 28 and that's only the beginning!

28 entities is no small application. That's on the cusp of being an enterprise-grade application. Not that this is a hard rule, but as a personal rule of thumb I usually consider any codebase with over 30 entities to definitely warrant a more robust architecture due to the expected size of the codebase.

The more complex your application is (which is heavily correlated with the number of entities), the more overhead and management you're going to need. This is just a fact of life.
You can cut corners now, but it's going to bite you in the end. The more complex your application (and again, the more entities you have), the harder it's going to bite you.

I ended up having 28 separate repository interfaces, 28 separate service interfaces, 28 actual repositories, 28 actual service classes.

You've falling into what I like to call "the Foo trap". You have a Foo entity with a FooService, FooRepository, FooController and a FooDTO. That's boilerplating, and more importantly it misses the domain-driven aspect of the architecture you're trying to build.

In DDD, you have to consider aggregates. Keeping it simple (there are plenty of online resources that go in-depth on this): an aggregate is a logical "subdomain" of your domain logic, which usually gets stored as multiple entities.

For example, Let's say you have a video rental service (it's an outdated concept, but a really simple business case to grasp). You may have many different tables, but these can be grouped into three subdomains: customers, videos and rentals. Each subdomain is likely backed by multiple tables:

  • Customer subdomain: Customer, Addresses, PaymentHistory, Cards, ...
  • Video subdomain: Tapes, Movies, Suppliers, Genres, AgeRatings, ...
  • Rental subdomain: Rental, LateFees, DamageReports, Payments, ...

When you say you have 28 entities, 28 repositories and 28 services, what you're telling me is that each and every entity you have is in its own subdomain. That is indicative of underdeveloping your domain architecture and not analyzing the larger business cases that your application is made up of.

What you're doing right now is building a code-shell based on a database model, and modeling each and every layer based on your data model. Don't get me wrong, that can occasionally be the right approach when e.g. your REST API acts merely as a database proxy client with no real business logic inbetween, but then your architecture does not match your business case.

Much more likely, your application has business value outside of its data storage capabilities, which means that your domain/application logic is going to be significantly different from your repository logic.

28 separate repository interfaces, [..] 28 actual repositories

In general, each distinct class is going to be backed by its own interface. That's just how it is.

I completely understand how this intuitively feels as a lot of duplication when you're new to clean coding. We all thought so when we first started doing it. But this is preparation for the future, when you're going to want to swap one class out with another, and you're going to be very happy that you started with the interfaces from the beginning.

Let's say you don't use interfaces here. You build your application, and the FooRepository is used everywhere it is needed.

Later during development, it turns out that your Foo requirements have changed (or were miscommunicated), and it turns out you need a ADifferentFooRepository to handle the Foo data storage logic. Now, you have to go back and change all of your code that used to touch FooRepository.

Compare this to when you had created your IFooRepository interface from the get go. Your FooRepository handles the data storage logic, but the rest of your codebase references IFooRepository, not the class itself.
When you now have to implement ADifferentFooRepository, all you need to do is ensure that it implements IFooRepository (which it should, if it replaces the old FooRepository). And all your existing code, which was referencing IFooRepository does not need to be changed, since your new ADifferentFooRepository actually implements that interface.

Note that it's easy to now argue "how hard can it be to change a class reference", but that's because any example I can give you here is always very simple, much more simple than your real-life codebase is going to be.

When your codebase gets to a size where you're no longer capable of intimately knowing every class in it and how they all relate to one another, which is very common for enterprise-grade applications, you're going to need to rely on the cleanliness of your coding to help you make changes to the codebase.

Pro tip: Most IDEs have a way of distilling an interface from a class (or vice versa: implementing an existing interface on a class). Just write the class and generate the interface from it using a few button presses (or vice versa: auto generate the interface implementation of a class). This mostly negates the argument that writing the additional interfaces is a cumbersome process, while keeping your code as clean as possible.

However, something inside tells me I've done a lot of unnecessary work

Frame challenge: it currently looks unnecessary, because you haven't yet reached the point where your current (extra) effort is paying back dividends. The good part is at the end.

which either could be simplified

"Simple to write" is not the same as "simple to read/change". Code is read/changed much more than it is written. Therefore, I'd much rather spend some more effort on the initial writing, if it consistently lowers the effort needed to read/change the code in the future.

A small effort now will reap bigger rewards in the future.

Hence, I am here seeking advice if this article is guiding me in the right direction.

There is no one-size-fits-all solution. The article is roughly in the right direction, but without knowing your codebase and business case, it's impossible to say if this is the best possible approach for you.

That being said, clean coding practices are generally advisable to a large proportion of codebases, so it's a good start. But your particular situation could always warrant changing part of the approach to better suit your needs.

That need to adapt will point itself out over time. Don't try to do it perfect the first time. Just build something that works and prepare for needing to change/improve things later on.
Clean coding doesn't tell you how to write things correctly. Clean coding just makes it easier to change something in the future, when it turns out you've either made a mistake or the requirements have changed.

The issues you've currently mentioned are not "issues" in the sense that they are a matter of you doubting (or not understanding) the usefulness of clean coding practices; rather than actual proof that you should do things differently.

7
  • ** Comment 1/4 - **As I read and watch more about the subject, I am quite close to coming to a conclusion that the matter is not the answer to the question but rather whether it is the right question to ask or not. I am a single dev working on simple to mid sized projects that are not for any customers but for my own use within my own businesses. I have and will always (until they are replaced by their own predecessors) use SQL server, EF Commented Oct 30, 2020 at 13:31
  • ** Comment 2/4 - **The issue is whether implementing this sort of architecture (fully/partially) has a "VALUE" or not for a particular case/project. In my case, given the facts above, introducing a repository pattern is a burdon on me causing a lot of time consuming work and I was thinking to get my services layer couple with EF directly would be a better choice.. Commented Oct 30, 2020 at 13:31
  • ** Comment 3/4 - ** Until.... You identified my problem - The Foo Trap.. I am definitely in it. In my project, my "subdomains" are Store, Product and Customer. Remaining are sub classes such as StoreOpeningHour.cs, ProductPackagingOption.cs which 99% of the time can be etc... I ended up just like you described above. 99% of the time all the I am not moaning about spending time. I am trying to learn here but I also believe in spending time efficiently. Commented Oct 30, 2020 at 13:31
  • ** Comment 4/4 - **I am not an experienced developer. The unknowns to approaching the problem - let alone identifying it - is a very steep journey for me. Guidance in starting at the right point is all I want. So, I'll now try to eliminate all the relational entities as repositories and seek ways of manipulating them through the main object they belong to.. E.g. Instead of having a repository for the ProductPackaging separately, I will do the basic CRUD through the main Product by doing Product.ProductPackaging = new ProductPackaging {...} within the ProductRepository am I right? Commented Oct 30, 2020 at 13:32
  • 1
    Nearly a year later, I must say I have to accept this not as an answer but as a lecture as if I took it in a class. Did a lot of research, made a lot of sample projects, watched a lot of videos and read quite a lot of articles. It all now makes sense and I can't believe how my mindset was before. Thank you very much for what you have tought me! Commented Aug 11, 2021 at 8:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.