在3层体系结构中使用实体框架实现有界上下文

时间:2015-02-17 23:48:38

标签: entity-framework domain-driven-design n-tier-architecture bounded-contexts

我观看了Julie Lerman关于在企业应用程序中使用EF的视频。现在我正在开发一个使用“Bounded Contexts”的网站以及她在该系列中教过的其他内容。

问题是我不知道如何在我的“业务层”中使用有界上下文(BC)。为了更清楚:BL应该如何知道它应该使用哪个特定的BC。

假设UI从业务层请求产品列表。在BL中我有一个返回产品列表的方法:GetAll()。此方法不知道UI的哪个部分(站点管理员,主持人或公共用户)已请求产品列表。由于每个用户/场景都有自己的有界上下文,因此需要使用该相关上下文来提取列表。 BL如何选择合适的BC?

此外,我不希望UI层与数据层交互。

如何做到这一点?

1 个答案:

答案 0 :(得分:5)

如果业务层是指一个定义了所有业务规则的地方,那么这就是一个有限的上下文。

有界上下文从某个角度查看您的系统,以便可以以分区方式实现业务规则(目标是通过拆分到更小的块来更容易处理整个问题)。

http://martinfowler.com/bliki/BoundedContext.html

fowler - bounded contexts

<强>前端

因此,假设您有一个ASP MVC前端,这些控制器将调用您的用例/用户故事,这些故事通过标准的已知接口从域中显示。

public class UserController : Controller
{
    ICommandHandler<ChangeNameCommand> handler;

    public UserController(ICommandHandler<ChangeNameCommand> handler)
    {
        this.handler = handler;
    }

    public ActionResult ChangeUserName(string id, string name)
    {
        try
        {
            var command = new ChangeNameCommand(id,name);
            var data = handler.handle(command);
        }
        catch(Exception e)
        {
            // add error logging and display info
            ViewBag.Error = e.Message;
        }

        // everything went OK, let the user know
        return View("Index");
    }
}

域名申请(用例)

接下来,您将拥有一个实现用例的域应用程序入口点(这将是一个命令或查询处理程序)。

您可以直接调用此代码并让代码在前端应用程序中运行,或者您可以在其前面提供WebAPI或WCF服务呈现域应用程序服务。这并不重要,系统如何受到不信任取决于系统要求(从基础设施的角度来看,如果不需要,通常更容易分发)。

域应用程序层然后编排用户故事 - 它将新建存储库,获取实体,对它们执行操作,然后写回存储库。这里的代码不应该复杂或包含逻辑。

public class NewUserHandler : ICommandHandler<ChangeNameCommand>
{
    private readonly IRepository repository;

    public NewUserHandler(IRepository repository)
    {
        this.repository = repository;
    }

    public void Handle(ChangeUserName command)
    {
        var userId = new UserId(command.UserId);
        var user = this.repository.GetById<User>(userId);
        user.ChangeName(command.NewName);
        this.repository.Save(newUser);
    }
}

域名模型

他们自己的实体在域模型中实现自己的业务逻辑。您可能还拥有逻辑域服务,这些服务自然不适合单个实体。

public class User
{
    protected string Name;
    protected DateTime NameLastChangedOn;

    public ChangeName(string newName)
    {
        // not the best of business rules, just an example...
        if((DateTime.UtcNow - NameLastChangedOn).Days < 30)
        {
            throw new DomainException("Cannot change name more than once every 30 days");
        }

        this.Name = newName;
        this.NameLastChangedOn = DateTime.UtcNow;
    }
}

<强>基础设施

您将拥有实现代码的基础结构,以从后备存储中获取和检索实体。对你来说这是实体框架和DbContext(我上面的示例代码不使用EF,但你可以替换)。

回答您的问题 - 前端应用程序应该调用哪个有界的上下文?

不要让答案复杂或冗长,但我包含上面的代码来设置背景并希望让它更容易理解,因为我认为你使用的术语有点混淆。

在开始实现更多命令和查询处理程序时使用上面的代码,从前端应用程序调用有界上下文取决于用户希望执行的特定用户故事。

用户故事通常会聚集在不同的有界上下文中,因此您只需为实现所需功能的有界上下文选择命令或查询 - 不要担心使其变得更复杂。

让你试图解决的问题决定了映射,并且不要害怕这个映射可能会随着对你要解决的问题的洞察而改变。

<强>旁注

作为附注提及我发现有用的东西(我用EF开始我的DDD之旅)...在实体框架中,经常需要ORM概念,例如定义实体之间的映射关系和导航属性,以及会发生什么使用级联删除和更新。对我来说,这开始影响我设计我的实体的方式,而不是决定如何设计实体的问题。您可能会发现这个有趣:http://mehdi.me/ambient-dbcontext-in-ef6/

您可能还需要查看http://geteventstore.com和事件源,这可以消除ORM映射的任何麻烦(但需要增加复杂性和变通方法才能获得可接受的性能)。什么是最好的使用取决于具体情况,但知道所有选项总是好的。

我还使用SimpleInjector连接我的类并注入MVC​​控制器(作为预建的命令或查询处理程序),更多信息在这里:https://cuttingedge.it/blogs/steven/pivot/entry.php?id=91

使用IoC容器只是个人偏好而不是一成不变。

这本书也很精彩:https://vaughnvernon.co/?page_id=168

我提到了上面的内容,因为我开始使用EF开始我的DDD之旅以及你遇到的完全相同的问题。