4

I've been programming professionally for years now at several different companies and I consider myself to be a fairly competent programmer. However, of everywhere I've worked there are usually dozens of different software engineers and programmers with dozens of different coding styles, and patterns. I've read tons of literature on the subject of well designed applications, but I honestly don't think I've ever seen or fully implemented one myself. I'm curious if there are any real hands on examples of different design principles, especially applications implementing a proper service architecture to really get a feel for everything.

For example, our current application began with many different conflicting design ideas, but most of the original engineers have been let go. Now, I pretty much have free reign with a whole swath of very novice developers in which to help train and educate as they begin their careers. I'm trying to set a positive example by implementing easy to follow, but still robust designs.

As our application stands today we have somewhat of a legacy area and the new area. The legacy area consists of:

  • A repository layer (built with ADO.Net), but it's pretty tightly coupled and near impossible to test or write tests for.
  • A domain layer - Relies on the repositories, but intermingles logic between many different classes and areas
  • A model project - loosely reflects the database models
  • A viewmodel project
  • A web logic layer - one developer went through a detangled all web/business logic (somewhat) and placed it into its own project.
  • The web layer - contains controllers, views, and web apis.

The new stuff is built similar, but taking a different approach

  • Entity layer - This project solely contains our entity framework entities and contexts. It also has a factory method that returns the proper context and gets the connection string information from our configuration files for each edmx.
  • EF Repositories - Each repository handles a minor subset of functionality, pretty much only 1 entity type is interacted with in this layer unless certain joins are necessary.
  • Domain layer - This layer builds out units of work for each and every piece of functionality going forward. It doesn't have much/any crossover functionality and is very specialized.
  • Unit test project - The unit test project creates mock repositories and checks all the logic implemented in the domain layer.
  • Web layer - responsible for wiring up dependency injection and serving up views, controllers, and API methods.

In the new stuff, every single repository implements an interface (almost to a fault), and everything works via dependency injection. I find it rather straight forward, but I'm afraid I'm either doing too much or too little.

For example, lets say I have a context called MainContext. In my repository layer, I have a repository called GenericRepository. GenericRepository implements an interface called IGenericRepository that lays out methods such as Get, Save, etc. These methods are the same for most repositories, but can be overridden if necessary. It might look something like:

public class GenericRepository<TEntity> : GenericRepository<TEntity> where TEntity : class
{
    internal SchoolContext context;
    internal DbSet<TEntity> dbSet;

    public GenericRepository(SchoolContext context)
    {
        this.context = context;
        this.dbSet = context.Set<TEntity>();
    }

    public virtual void Insert(TEntity entity)
    {
        dbSet.Add(entity);
    }

    public virtual void Update(TEntity entityToUpdate)
    {
        dbSet.Attach(entityToUpdate);
        context.Entry(entityToUpdate).State = EntityState.Modified;
    }
    // -- more methods
}

All other repositories inherit from the GenericRepository, but they all also implement their own interface that also implements IGenericRepository.

At this point we are 2 or 3 interfaces deep, and I'm beginning to feel like it's a bit overkill.

Am I going overboard with the design? Are there any good, complete examples that illustrate the best path forward with a similar design?

3

The overall architecture you describe looks very similar to what I've seen for mid/large scale applications in a corporate environment with a moderate/high level of complexity. It takes experience and practice to see where it makes the most sense to add another layer of abstraction i.e. you start seeing where it makes sense after getting burned enough times.

Here are a few guiding principles that I use to decide between if something is over- vs under- engineered.

Does it encourage Automated Testing? Personally, I am a large proponent for automated testing (unit and integration) to ensure that whoever maintains the code after me can run the tests and be reasonably certain that (1) the code works and (2) understand the expected behavior and failure conditions. This can only be done when the code base is broken up enough facilitate meaningful tests. Code that is broken up too much leaves nothing of substance to be tested.

Does it make it easier to swap out components? It may help to identify the different tools the application depends on that could be reasonably changed in the future. This will make future changes at least less painful. Wrappers can be especially helpful here. Examples:

When a piece of software gets so brittle that it can no longer be updated/changed/enhanced, it's only a matter of time before it's obsolete.

Does the abstraction add value? It's fine for there to be interfaces 2 or 3 levels deep as long as there is a purpose for it. Think of the automated tests you have. Their purpose is to make your code (unit) testable which provides value by documenting, establishing expected behavior, and forcing organized coding. Tests for the sake of testing only add additional clutter. Likewise, the level of abstractions are helpful if they can enable you to work cleaner, support testing, and decouple code where it's reasonable to change (see seams).

You can use the "rule of 3" to determine when something can be refactored or made reusable.

Use meaningful names - what names you use can almost be as important as the methods/abstractions themselves. For example, interfaces and implementation don't need to be named the same. For example, you may have an IDatabaseRepository which is implemented by OracleRepository and SqlRepository. The names tell something about why the abstraction exists and how the implementations are different. IGenericShape and GenericShape don't tell you as much as IShape and Triangle do.

0

I must say that the Repository pattern can be confusing. I have been using it in two applications in different way. One is 95% data reading while the other one is CRUD.

I am using your example in CRUD application where I am using the IOC to inject the interfaces into the services and then in UnitTests I mock the repository. This works quite well. But I would still say that it is quite coupled with EF as I have to instantiate the context when using the repository.

In other application I am not using the repository pattern in the same way as I am using Dapper to get speed and I do not need CRUD. In this case there are hundereds of repositories and are basically using the CQRS to get the data. This project consists mostly of integration tests as the actual data fetched is using complex queries and we need to guarantee the correctness.

My point is that separating the code into a repository that gets has the responsibility to connect to the data is a good separation for many reasons no matter what sub-pattern you use.

protected by gnat Aug 1 '18 at 5:59

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

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