洋葱架构中视图的放置 models/DTOs

Placement of view models/DTOs in onion architecture

我目前正在重构一个ASP.NET MVC项目以使用onion arcitecture,因为它似乎适合未来发展的需要。

我已经设置了我认为需要使用的图层,我的解决方案现在如下所示:

所以,基本上据我了解,ClientName.Core项目根本不应该有任何对其他项目的引用。 ClientName.Infrastructure 应该引用 ClientName.CoreClientName.Core 上的 Interfaces 文件夹定义了 ClientName.Infrastructure 项目中的服务,我的 DbContext 和域实体是分开的,因此只有实体在核心项目中。

我 运行 我的头靠在墙上,ClientName.Infrastructure 项目不应该 return 域实体到任何调用它的客户端。这将在核心项目和任何 UI 之间创建一个参考,"violates" 洋葱原理。正如我所读到的,解决这个问题的一种方法是使基础设施服务 return DTO 代替。但是,如果我正在 returning,即来自 PersonService class 的 PersonDtoPersonDto 对象需要被 ClientName.Core 知道项目,因为那是接口所在的位置。

所以问题是:我应该把用于 UI/client 的 DTO/ViewModel/other 模型放在哪里?我是否创建一个单独的 class 库来保存这些模型并让 UI、基础设施和核心项目都引用它?

任何 help/hint 非常感谢,因为我对此有点困惑 ;-)

提前致谢。

编辑

基于 Euphorics 的回答,我在这里编写示例代码只是为了检查我是否做对了,也许还有一些后续问题。

所以基本上,在我的 ClientName.Core 层中,我有包含业务逻辑的实体,即 PersonFirm 实体可能如下所示:

(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 纯粹主义者可能不同意我的看法。