在 3 层架构中使用 Entity Framework 实现限界上下文
Implementing Bounded Context with Entity Framework in a 3-Layer Architecture
我看过 Julie Lerman 关于在企业应用程序中使用 EF 的视频。现在我正在使用 "Bounded Contexts" 和她在该系列中教授的其他内容开发一个网站。
问题是我不知道如何在 "Business Layer" 中使用限界上下文 (BC)。更清楚地说:BL应该如何知道它应该使用哪个特定的BC。
假设 UI 从业务层请求产品列表。在 BL 中,我有一个方法 returns 产品列表:GetAll()
。此方法不知道 UI 的哪一部分(站点管理员、版主或 public 用户)请求了产品列表。由于每个 user/scenario 都有自己的限界上下文,因此需要使用该相关上下文来拉取列表。 BL应该如何选择合适的BC?
此外,我不希望 UI 层与数据层交互。
如何做到这一点?
如果您所说的业务层是指定义所有业务规则的地方,那么这就是限界上下文。
限界上下文从特定角度审视您的系统,以便可以以分隔的方式实施业务规则(目标是通过拆分成更小的块更容易处理整体问题)。
http://martinfowler.com/bliki/BoundedContext.html
前端
因此,假设您有一个 ASP MVC 前端,这个控制器将调用您的使用 cases/user 故事,这些故事是从域中呈现的,通过标准的已知接口调用。
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 服务 presenting 域应用服务。这并不重要,系统如何不受信任取决于系统要求(从基础架构的角度来看,如果不需要则不分发通常更简单)。
域应用层然后编排用户故事——它将新建存储库、获取实体、对它们执行操作,然后写回存储库。这里的代码不要太复杂,不要有逻辑。
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;
}
}
基础设施
您将拥有实现代码以从后备存储中获取和检索实体的基础结构。对您来说,这是 Entity Framework 和 DbContext(我上面的示例代码没有使用 EF,但您可以替代)。
回答你的问题——前端应用程序应该调用哪个限界上下文?
不是为了让答案变得复杂或冗长,但我包含了上面的代码来设置背景,希望能让它更容易理解,因为我认为你使用的术语有点混乱。
使用上面的代码,当您开始实现更多命令和查询处理程序时,从您的前端应用程序调用哪个限界上下文取决于用户希望执行的特定用户故事。
用户故事通常会聚集在不同的限界上下文中,因此您只需 select 命令或查询实现所需功能的限界上下文 - 不要担心让它变得比那。
让您尝试解决的问题决定映射,不要担心此映射可能会随着对您要解决的问题的洞察力的提高而改变。
旁注
顺便提一下我发现有用的东西(我从 EF 开始我的 DDD 之旅)...... entity framework 有一些经常需要的 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 之旅时提到了上面的内容,并且遇到了与您完全相同的问题。
我看过 Julie Lerman 关于在企业应用程序中使用 EF 的视频。现在我正在使用 "Bounded Contexts" 和她在该系列中教授的其他内容开发一个网站。
问题是我不知道如何在 "Business Layer" 中使用限界上下文 (BC)。更清楚地说:BL应该如何知道它应该使用哪个特定的BC。
假设 UI 从业务层请求产品列表。在 BL 中,我有一个方法 returns 产品列表:GetAll()
。此方法不知道 UI 的哪一部分(站点管理员、版主或 public 用户)请求了产品列表。由于每个 user/scenario 都有自己的限界上下文,因此需要使用该相关上下文来拉取列表。 BL应该如何选择合适的BC?
此外,我不希望 UI 层与数据层交互。
如何做到这一点?
如果您所说的业务层是指定义所有业务规则的地方,那么这就是限界上下文。
限界上下文从特定角度审视您的系统,以便可以以分隔的方式实施业务规则(目标是通过拆分成更小的块更容易处理整体问题)。
http://martinfowler.com/bliki/BoundedContext.html
前端
因此,假设您有一个 ASP MVC 前端,这个控制器将调用您的使用 cases/user 故事,这些故事是从域中呈现的,通过标准的已知接口调用。
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 服务 presenting 域应用服务。这并不重要,系统如何不受信任取决于系统要求(从基础架构的角度来看,如果不需要则不分发通常更简单)。
域应用层然后编排用户故事——它将新建存储库、获取实体、对它们执行操作,然后写回存储库。这里的代码不要太复杂,不要有逻辑。
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;
}
}
基础设施
您将拥有实现代码以从后备存储中获取和检索实体的基础结构。对您来说,这是 Entity Framework 和 DbContext(我上面的示例代码没有使用 EF,但您可以替代)。
回答你的问题——前端应用程序应该调用哪个限界上下文?
不是为了让答案变得复杂或冗长,但我包含了上面的代码来设置背景,希望能让它更容易理解,因为我认为你使用的术语有点混乱。
使用上面的代码,当您开始实现更多命令和查询处理程序时,从您的前端应用程序调用哪个限界上下文取决于用户希望执行的特定用户故事。
用户故事通常会聚集在不同的限界上下文中,因此您只需 select 命令或查询实现所需功能的限界上下文 - 不要担心让它变得比那。
让您尝试解决的问题决定映射,不要担心此映射可能会随着对您要解决的问题的洞察力的提高而改变。
旁注
顺便提一下我发现有用的东西(我从 EF 开始我的 DDD 之旅)...... entity framework 有一些经常需要的 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 之旅时提到了上面的内容,并且遇到了与您完全相同的问题。