.NET EF - 如何使用干净架构方法避免不同数据库上下文中相同模型的代码重复
我正在为基于 .NET 6 的多租户应用程序构建一个 Web API 后端,并且我遇到了一个架构问题,自过去几周以来,这个问题让我发疯,所以我非常感谢任何提示和建议。
整个解决方案是建立在经典的清洁架构(层:API、域、应用程序、基础设施、共享)中,这不是我认为最好的想法,但恐怕现在是不可逆转的。无论如何,我想努力拯救我迄今为止所创造的一切。
在应用程序中,我有 2 个数据库(后面的代码中包含实体框架):
- 数据库 A:租户用户范围 - 每个表中都有
TenantId
列,用于在 SQL 级别实现的 RLS 机制,以防止租户间的交互数据泄漏 - 数据库 B:内部用户范围 - 无 RLS 机制
问题在于两个数据库共享大量共同的结构和逻辑。
当前模型(数据库和域)的简化示例:
数据库 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() { ... }
}
数据库 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() { ... }
}
目前,使用 CQRS (MediatR)我必须实现如下逻辑:
- “CreateTenantEmployeeCommand”
- “CreateInternalEmployeeCommand”
- “GetTenantEmployeeDataQuery”
- “GetInternalEmployeeDataQuery”
等。
这样我正在创建大量重复代码。
我不想为这些类创建通用接口,因为我认为这可能会在未来的某个时候终止应用程序的开发(模型可能随时变得有点不同)。
有一个想法是在基础设施层中创建贫乏的数据库模型,如下所示
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; }
}
然后创建公共域模型(在域层中),如下所示:
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() { ... }
}
在这种方法中,我可以将域逻辑保留在一个位置并使用接受域模型的通用存储库模式用于输入和输出以及将域模型映射到数据库模型并返回的内部存储库逻辑。这种解决方案仍然在某些区域给我留下了重复的代码,但我想它可以让我在应用程序和域层中阻止它。
尽管如此,我还是想确保我没有错过任何更好的解决方案。
I am building a 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 advice.
Whole solution is build in classic Clean Architecture (layers: API, Domain, Application, Infrastructure, Shared), which wasn't the greatest idea I suppose, but I'm afraid 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
column in each table for RLS mechanism 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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
如果您有太多重复,那么您认为应该合并代码是正确的。如果两个模型都应该能够具有通用代码,和特定代码,那么解决您的问题的方法可能是多态性。
我建议有两个新类
Domain.TenantEmployee
和Domain.InternalEmployee
都继承自抽象Domain.Employee
。您可以将常见行为放在父类中,将特定行为放在子类中。然后,您的基础设施层可以将
Domain.TenantEmployee
与DatabaseA.TenantEmployee
相互转换,将Domain.InternalEmployee
从/到DatabaseB.InternalEmployee< 相互转换/代码>。
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
andDomain.InternalEmployee
both inheriting from an abstractDomain.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/toDatabaseA.TenantEmployee
andDomain.InternalEmployee
from/toDatabaseB.InternalEmployee
.