Home > OS >  .NET EF - how to avoid code duplication for identical models in different db contexts using Clean Ar
.NET EF - how to avoid code duplication for identical models in different db contexts using Clean Ar

Time:03-23



I am building Web API backend for multitenant, .NET 6 based, application and I have encountered an architectural problem which drives me crazy since last couple of weeks, so I would much appreciate ANY hints and advices.
Whole solution is build in classic Clean Architecture (layers: API, Domain, Application, Infrastructure, Shared), which was't the greatest idea I suppose, but I'm affraid it is irreversible by now. Anyway, I would like to make an effort to save all I have created so far.

In the application I have 2 databases (with Entity Framework in the code behind): Database A: Tenant users scope - with TenantId field in each table for RLS mechanizm implemented on the SQL level to prevent inter-tenant data leaks Database B: Internal users scope - no RLS mechanism

The problem is both databases share great amount of common structures and logic.
Simplified example of current models (both database and domain):
Database A:

public class TenantEmployee {
  public Guid Id {get; protected set;}
  public string Name {get; protected set;}
  public string DepartmentId {get; protected set;}

  public void SetName(string name) { ... }
  public void SetDepartment(string id) { ... }
  public int GetWorkingHours() { ... }
}

Database B:

public class InternalEmployee {
  public Guid Id {get; protected set;}
  public string Name {get; protected set;}
  public string DepartmentId {get; protected set;}

  public void SetName(string name) { ... }
  public void SetDepartment(string id) { ... }
  public int GetWorkingHours() { ... }
}

Currently, using CQRS (MediatR) I have to implement logic as follows:

  • "CreateTenantEmployeeCommand"
  • "CreateInternalEmployeeCommand"
  • "GetTenantEmployeeDataQuery"
  • "GetInternalEmployeeDataQuery"

etc.

This way I'm creating a lot of code duplication. I do not want to create common interfaces for those classes because I think this may kill the application's development in some point in the future (Models may become a little bit different anytime).

There was an idea to create anemic database models in the infrastructure layer, like this

internal class TenantEmployee {
  public Guid Id {get; set;}
  public string Name {get; set;}
  public string DepartmentId {get; set;}
}
internal class InternalEmployee {
  public Guid Id {get; set;}
  public string Name {get; set;}
  public string DepartmentId {get; set;}
}

Then create common domain model (in Domain layer) like that:

public class Employee {
  public Guid Id {get; protected set;}
  public string Name {get; protected set;}
  public string DepartmentId {get; protected set;}

  public void SetName(string name) { ... }
  public void SetDepartment(string id) { ... }
  public int GetWorkingHours() { ... }
}

And in this approach I could keep domain logic in one place and use generic repository pattern which accepts Domain model for inputs and outputs and inner repository logic which maps domain models to db models and back. This kind of solution still leaves me with code duplicates in some areas but it will allow me to prevent it in Application and Domain layers I suppose.

Still, I'm would like to make sure I did not miss any better solutions.

CodePudding user response:

This way I'm creating a lot of code duplication. I do not want to create common interfaces for those classes because I think this may kill the application's development in some point in the future (Models may become a little bit different anytime).

If you have too much duplication, you are right in thinking you should merge your code. If both model should be able to have common code, and specific code, then the solution to your problem is probably polymorphism.

I would suggest to have two new classes Domain.TenantEmployee and Domain.InternalEmployee both inheriting from an abstract Domain.Employee. You can put common behavior in the parent class, and specific one in the child ones.

Then your infrastructure layer can convert Domain.TenantEmployee from/to DatabaseA.TenantEmployee and Domain.InternalEmployee from/to DatabaseB.InternalEmployee.

  • Related