洋葱架构中视图的放置 models/DTOs
Placement of view models/DTOs in onion architecture
我目前正在重构一个ASP.NET MVC项目以使用onion arcitecture,因为它似乎适合未来发展的需要。
我已经设置了我认为需要使用的图层,我的解决方案现在如下所示:
所以,基本上据我了解,ClientName.Core
项目根本不应该有任何对其他项目的引用。 ClientName.Infrastructure
应该引用 ClientName.Core
。 ClientName.Core
上的 Interfaces 文件夹定义了 ClientName.Infrastructure
项目中的服务,我的 DbContext 和域实体是分开的,因此只有实体在核心项目中。
我 运行 我的头靠在墙上,ClientName.Infrastructure
项目不应该 return 域实体到任何调用它的客户端。这将在核心项目和任何 UI 之间创建一个参考,"violates" 洋葱原理。正如我所读到的,解决这个问题的一种方法是使基础设施服务 return DTO 代替。但是,如果我正在 returning,即来自 PersonService
class 的 PersonDto
,PersonDto
对象需要被 ClientName.Core
知道项目,因为那是接口所在的位置。
所以问题是:我应该把用于 UI/client 的 DTO/ViewModel/other 模型放在哪里?我是否创建一个单独的 class 库来保存这些模型并让 UI、基础设施和核心项目都引用它?
任何 help/hint 非常感谢,因为我对此有点困惑 ;-)
提前致谢。
编辑
基于 Euphorics 的回答,我在这里编写示例代码只是为了检查我是否做对了,也许还有一些后续问题。
所以基本上,在我的 ClientName.Core
层中,我有包含业务逻辑的实体,即 Person
和 Firm
实体可能如下所示:
(Exists in ClientName.Core/Entities)
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public Firm Firm { get; set; }
}
(Exists in ClientName.Core/Entities)
Public class Firm
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<Person> Employees { get; set; }
public IEnumerable<Person> GetEmployeesByName(string name)
{
return Employees.Where(x => x.Name.Equals(name));
}
public void AddEmployee(Person employee)
{
Employees.Add(employee);
}
public void CreateFirm(Firm firm)
{
// what should happen here? Using entity framework, I have no reference to the DbContext here...
}
}
(Exists in ClientName.Infrastructure/Services)
public class FirmService : IFirmService
{
private readonly IDbContext _context;
public FirmService(IDbContext context)
{
_context = context;
}
public Firm GetById(int firmId)
{
return _context.Firms.Find(firmId);
}
// So basically, this should not call any domain business logic?
public void CreateFirm(CreateFirmFormViewModel formViewModel)
{
Firm firm = new Firm()
{
Name = formViewModel.Name;
}
_context.Firms.Add(firm);
_context.SaveChanges();
}
public IEnumerable<Person> GetEmployeesByName(int firmId, string name)
{
Firm firm = _context.Firms.Find(firmId);
return firm.GetEmployeesByName(name);
}
}
我是否正确地认为每个读取查询都应该直接在实体上定义(可能作为实体扩展,因为我使用的是 Entity Framework)并且任何 create/update/delete 只会在服务中发生在基础设施层?
读取(即 IEnumerable<Person> GetEmployeesByName(int firmId, string name)
方法)方法是否也应该在 FirmService
interface/class 上?
再次感谢:-)
首先,您不是从 PersonService
return ing Person
,而是 IPersonService
。这是巨大的概念差异。 PersonService 仅实现 IPersonService
定义的内容。它不关心它使用什么接口或对象。
简而言之,核心项目应该包括业务实体中的所有业务逻辑。如果您没有任何实际逻辑,那将只是普通的 DTO。然后,Infrastructure
将负责 saving/loading 数据库中的那些实体。如果它使用自己的实体创建自己的模型,或者如果它重用核心定义的实体,则 Infrastructure
的实现细节。同时,UI
(或 Web)项目将与 Core
项目中的实体一起工作,但它不会关心持久化是如何发生的。它只想 "do the business".
这里的关键点是,核心(或业务逻辑)是可自动测试的,无需涉及 UI
或数据库代码。只要你能做到,有些DTO在哪里都无所谓。
编辑:
这开始变得困难了。您遇到了相互冲突的设计要求。一方面,你有清晰的领域设计。另一方面,您希望您的模型使用 EF 持久化。有一件事很清楚:您将妥协并扭曲您的域模型,以便它可以在 EF 中使用。
现在讨论具体问题。第一个是公司的创建。在这里,您在域理解的 "new Firm" 之间存在冲突。例如。确保新公司的名称有效。另一方面,您想要创建实例并让 EF 知道它。我这样做的方法是从 Firm
class 中删除 CreateFirm
并在 IFirmService
中更改 CreateFirm
以便它只接受创建新文件所必需的参数Firm
(例如,只是名称)和 return 新创建的公司。此外,存储库不应调用域的想法是错误的。例如,存储库可能会调用域来验证它创建的公司是否有效,如果无效,则不要将其添加到 EF 中。
我看到的第二个问题是在公司中保留员工集合。虽然这对域来说很好,但对持久性来说却是灾难(除非你认为延迟加载在这种情况下很好)。再次。没有简单的方法可以满足 "Firm has collection of Employees" 的域要求和将实体持久保存到数据库中的能力。第一个选择是不要这个集合并将其移动到 IFirmService
,在那里它可以被正确实现。第三种选择是最极端的。那就是让 Firm
成为一个抽象 class,同时让所有 "get" 成为一个抽象方法,在基础设施项目中创建它的实现,并使用 EF 上下文实现 get 方法具体实例会有。此外,只有在基础设施中创建了 Firm
的具体实例,这才是可能的,这就是我上面所建议的。
我个人喜欢第三种选择,因为它使方法保持美观和内聚。但一些 EF 纯粹主义者可能不同意我的看法。
我目前正在重构一个ASP.NET MVC项目以使用onion arcitecture,因为它似乎适合未来发展的需要。
我已经设置了我认为需要使用的图层,我的解决方案现在如下所示:
所以,基本上据我了解,ClientName.Core
项目根本不应该有任何对其他项目的引用。 ClientName.Infrastructure
应该引用 ClientName.Core
。 ClientName.Core
上的 Interfaces 文件夹定义了 ClientName.Infrastructure
项目中的服务,我的 DbContext 和域实体是分开的,因此只有实体在核心项目中。
我 运行 我的头靠在墙上,ClientName.Infrastructure
项目不应该 return 域实体到任何调用它的客户端。这将在核心项目和任何 UI 之间创建一个参考,"violates" 洋葱原理。正如我所读到的,解决这个问题的一种方法是使基础设施服务 return DTO 代替。但是,如果我正在 returning,即来自 PersonService
class 的 PersonDto
,PersonDto
对象需要被 ClientName.Core
知道项目,因为那是接口所在的位置。
所以问题是:我应该把用于 UI/client 的 DTO/ViewModel/other 模型放在哪里?我是否创建一个单独的 class 库来保存这些模型并让 UI、基础设施和核心项目都引用它?
任何 help/hint 非常感谢,因为我对此有点困惑 ;-)
提前致谢。
编辑
基于 Euphorics 的回答,我在这里编写示例代码只是为了检查我是否做对了,也许还有一些后续问题。
所以基本上,在我的 ClientName.Core
层中,我有包含业务逻辑的实体,即 Person
和 Firm
实体可能如下所示:
(Exists in ClientName.Core/Entities)
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public Firm Firm { get; set; }
}
(Exists in ClientName.Core/Entities)
Public class Firm
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<Person> Employees { get; set; }
public IEnumerable<Person> GetEmployeesByName(string name)
{
return Employees.Where(x => x.Name.Equals(name));
}
public void AddEmployee(Person employee)
{
Employees.Add(employee);
}
public void CreateFirm(Firm firm)
{
// what should happen here? Using entity framework, I have no reference to the DbContext here...
}
}
(Exists in ClientName.Infrastructure/Services)
public class FirmService : IFirmService
{
private readonly IDbContext _context;
public FirmService(IDbContext context)
{
_context = context;
}
public Firm GetById(int firmId)
{
return _context.Firms.Find(firmId);
}
// So basically, this should not call any domain business logic?
public void CreateFirm(CreateFirmFormViewModel formViewModel)
{
Firm firm = new Firm()
{
Name = formViewModel.Name;
}
_context.Firms.Add(firm);
_context.SaveChanges();
}
public IEnumerable<Person> GetEmployeesByName(int firmId, string name)
{
Firm firm = _context.Firms.Find(firmId);
return firm.GetEmployeesByName(name);
}
}
我是否正确地认为每个读取查询都应该直接在实体上定义(可能作为实体扩展,因为我使用的是 Entity Framework)并且任何 create/update/delete 只会在服务中发生在基础设施层?
读取(即 IEnumerable<Person> GetEmployeesByName(int firmId, string name)
方法)方法是否也应该在 FirmService
interface/class 上?
再次感谢:-)
首先,您不是从 PersonService
return ing Person
,而是 IPersonService
。这是巨大的概念差异。 PersonService 仅实现 IPersonService
定义的内容。它不关心它使用什么接口或对象。
简而言之,核心项目应该包括业务实体中的所有业务逻辑。如果您没有任何实际逻辑,那将只是普通的 DTO。然后,Infrastructure
将负责 saving/loading 数据库中的那些实体。如果它使用自己的实体创建自己的模型,或者如果它重用核心定义的实体,则 Infrastructure
的实现细节。同时,UI
(或 Web)项目将与 Core
项目中的实体一起工作,但它不会关心持久化是如何发生的。它只想 "do the business".
这里的关键点是,核心(或业务逻辑)是可自动测试的,无需涉及 UI
或数据库代码。只要你能做到,有些DTO在哪里都无所谓。
编辑:
这开始变得困难了。您遇到了相互冲突的设计要求。一方面,你有清晰的领域设计。另一方面,您希望您的模型使用 EF 持久化。有一件事很清楚:您将妥协并扭曲您的域模型,以便它可以在 EF 中使用。
现在讨论具体问题。第一个是公司的创建。在这里,您在域理解的 "new Firm" 之间存在冲突。例如。确保新公司的名称有效。另一方面,您想要创建实例并让 EF 知道它。我这样做的方法是从 Firm
class 中删除 CreateFirm
并在 IFirmService
中更改 CreateFirm
以便它只接受创建新文件所必需的参数Firm
(例如,只是名称)和 return 新创建的公司。此外,存储库不应调用域的想法是错误的。例如,存储库可能会调用域来验证它创建的公司是否有效,如果无效,则不要将其添加到 EF 中。
我看到的第二个问题是在公司中保留员工集合。虽然这对域来说很好,但对持久性来说却是灾难(除非你认为延迟加载在这种情况下很好)。再次。没有简单的方法可以满足 "Firm has collection of Employees" 的域要求和将实体持久保存到数据库中的能力。第一个选择是不要这个集合并将其移动到 IFirmService
,在那里它可以被正确实现。第三种选择是最极端的。那就是让 Firm
成为一个抽象 class,同时让所有 "get" 成为一个抽象方法,在基础设施项目中创建它的实现,并使用 EF 上下文实现 get 方法具体实例会有。此外,只有在基础设施中创建了 Firm
的具体实例,这才是可能的,这就是我上面所建议的。
我个人喜欢第三种选择,因为它使方法保持美观和内聚。但一些 EF 纯粹主义者可能不同意我的看法。