【问题标题】:Placement of view models/DTOs in onion architecture在洋葱架构中放置视图模型/DTO
【发布时间】:2015-11-08 18:44:55
【问题描述】:

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

我已经设置了我认为需要使用的层,我的解决方案现在看起来像这样:

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

我头疼的地方是,ClientName.Infrastructure 项目不应该将域实体返回给调用它的任何客户端。这将在核心项目和任何“违反”洋葱原则的 UI 之间创建一个引用。正如我所读到的,解决这个问题的一种方法是让基础设施服务返回 DTO。但是,如果我从 PersonService 类返回 PersonDto,则 PersonDto 对象需要被 ClientName.Core 项目知道,因为这是接口所在的位置。

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

非常感谢任何帮助/提示,​​因为我对此有点困惑 ;-)

提前致谢。

编辑

基于 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);
    }
}

我是否正确,每个读取查询都应该直接在实体上定义(可能作为实体扩展,因为我使用的是实体框架)并且任何创建/更新/删除都只会发生在基础设施层的服务中?

读取(即IEnumerable&lt;Person&gt; GetEmployeesByName(int firmId, string name) 方法)方法是否也应该在FirmService 接口/类上?

再次感谢:-)

【问题讨论】:

    标签: c# asp.net-mvc entity-framework onion-architecture


    【解决方案1】:

    首先,您不是从PersonService 返回Person,而是从IPersonService。这是巨大的概念差异。 PersonService 仅实现 IPersonService 定义的内容。它不关心它使用什么接口或对象。

    简而言之,Core 项目应该包含业务实体中的所有业务逻辑。如果您没有任何实际逻辑,那将只是普通的 DTO。然后,Infrastructure 将负责从数据库中保存/加载这些实体。如果它使用自己的实体创建自己的模型,或者如果它重用由核心定义的模型是Infrastructure 的实现细节。同时,UI(或 Web)项目将与 Core 项目中的实体一起工作,但它不会关心持久性是如何发生的。它只是想“做生意”。

    这里的关键点是核心(或业务逻辑)是可自动测试的,而不涉及UI 或数据库代码。只要你能够做到这一点,一些 DTO 在哪里都没有关系。

    编辑:

    这开始变得困难了。您遇到了相互冲突的设计要求。一方面,你有明确的领域设计。另一方面,您希望您的模型可以使用 EF 持久化。有一点很清楚:您将妥协并扭曲您的域模型,以便它可以在 EF 中使用。

    现在谈谈具体问题。第一个是创建公司。在这里,您在域所理解的“新公司”之间存在冲突。例如。确保新公司的名称有效。另一方面,您希望创建实例并让 EF 知道它。我这样做的方法是从Firm 类中删除CreateFirm 并在IFirmService 中更改CreateFirm,以便它只接受创建新Firm 所需的参数(例如,只是名称)并返回新成立的公司。此外,存储库不应调用域的想法是错误的。例如,存储库可能会调用域来验证它创建的公司是否有效,如果不是,则不要将其添加到 EF 中。

    我看到的第二个问题是将员工收集保存在公司中。虽然这对域来说很好,但对于持久性来说却是灾难(除非您认为延迟加载在这种情况下很好)。再次。没有简单的方法来满足“公司有员工集合”的领域要求和将实体持久化到数据库中的能力。第一个选择是不要拥有这个集合并将其移动到IFirmService,在那里可以正确实施。第三种选择是最极端的。那就是使Firm成为一个抽象类,同时使所有“get”成为一个抽象方法,在基础设施项目中创建它的实现,并使用具体实例将具有的EF上下文来实现get方法。此外,只有在基础架构中创建了Firm 的具体实例,这才有可能实现,这就是我上面建议的。

    我个人喜欢第三个选项,因为它使方法保持美观和连贯性。但是一些 EF 纯粹主义者可能不同意我的观点。

    【讨论】:

    • 嗨@Euphoric,感谢您的回答。我刚刚用一些简单的代码和一些后续问题更新了我的问题,以确保我做对了。
    猜你喜欢
    • 1970-01-01
    • 2011-10-09
    • 2021-04-05
    • 2014-10-15
    • 1970-01-01
    • 2014-06-22
    • 2015-03-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多