我正在使用DDD原则制作应用程序.在尽可能多地思考一切之后,我就开始制作我的有界背景了.我还没有设置最终结构,但截至目前,我的应用程序将包含以下有界上下文:
我希望它尽可能地可插拔,所以我可以单独开发和维护它们.他们可能会公开WCF或Web API来与它们进行交互.
我将使用Udi Dahans实现一个简单的CQRS模式.我不想一直使用事件源,消息总线等,因为这不是一个高度协作的应用程序(少于1000个用户,他们不太可能编辑相同的小数据集),这将增加了许多不必要的复杂性.
那么问题:
所有BC的员工和部门实体都很常见,如何建模?
部门是组织结构的一部分,因此在员工管理BC中,员工在一个部门工作,他们可以管理一个部门,他们有他们所从事过的部门的历史.
在购买BC商品是从部门购买,并交付给部门.Suplliers与不同部门签订了不同的合同.
在存档中,一些信息将被存档并绑定到一个部门,依此类推.
这同样适用于员工.
如何从有界上下文中保留数据?
它们可以映射到同一个数据库,也可以各自拥有自己的数据库.
到目前为止我已经做了一些想法
如何建模
我应该再建一个名为"公司"或"组织"的BC并在那里管理部门吗?
根据上面引用的Udi Dahans文章,我应该为每个BC制作一个部门实体和一个雇员实体,只需要我所需要的BC字段和行为.这听起来很合理,但后来我正在考虑如何实际使用它,我无法弄明白.我需要访问其他地方管理的部门,但我究竟是如何做到这一点而不是混合我的BC?
如何使用?
假设我通过查询从somwhere获取我的部门列表.在UI中我得到了我想要购买的部门列表.这是该部门的第一次购买,因此采购BC还不了解这个部门......所以购买BC的部门对象将填充从其他BC维护的数据 - 所以我该如何坚持这一点?如果不存在,我需要添加一些信息,如送货地址和发票地址?
在"注册部门UI"中,我应该在所有BC上调用"RegisterDepartment"服务,然后确保这些服务与通过UI(MVC控制器)所做的所有更改同步吗?
与员工一样.我想知道哪个员工进行了购买或在存档中放了一些东西.所以我不知何故在这些BC中也需要一个员工对象,但是从不同的BC管理它们.
持久化
可以通过将不同的employee-objects映射到数据库中的同一个表来解决上面的一些挑战.购买BC和Archive BC不能注册新员工,而是将信息附加到那些在那里的人并将他们绑定到同一数据库中的其他对象.然后数据库会让所有BC仍然生活在同一个世界......
我需要建议,所以我最终不会制作一些以后很难维护的东西.
c# design-patterns domain-driven-design cqrs bounded-contexts
提出这个问题的原因是我一直想知道如何将所有这些不同的概念拼接在一起.有很多关于DDD,依赖注入,CQRS,SOA,MVC的例子和讨论,但没有那么多关于如何以灵活的方式将它们组合在一起的例子.
我的目标:
为了更容易提出具体问题,主要的架构现在看起来像这样:

该示例显示如何向员工添加注释.员工管理是一个有限的背景.员工有几个属性,其中包括ICollection<Note>.
绑定上下文是我理解分离代码的逻辑位置.每个BC都是一个模块.大多数时候,如果需要,我发现每个人都可以保证自己的UI(即某些模块可能适用于Windows手机).
Domain拥有所有业务逻辑.
基础结构包含存储库实现,以及用于发送邮件,保存不属于域的文件和实用程序的服务.我正在考虑制作一些常见的服务功能,我必须在几个领域(例如发送电子邮件)中使用它作为一种API,我可以参考这些API来保存一些代码在几个BC中实现相同的功能.
查询层包含除了GetById之外的所有查询,我需要在存储库中获取对象.查询层可以查询其他持久性实例,并且可能需要为每个UI更改一些.
Wcf或Web Api是我的应用层,它可能属于基础设施,而不属于外部.此服务还设置依赖项,因此所有UI需要做的是询问信息并发送命令.
该过程从蓝色箭头开始.阅读模型,因为那里有大部分信息.
在步骤1中,此示例中的EmployeeDto只是一些员工属性,用于向用户显示他们需要记录员工的信息(如关于新体验的说明或类似内容).
所以,问题是:
这个实现有什么问题吗?任何摔倒坑都需要注意?
你有什么好的例子可以帮助我理解所有这些概念应该如何协同工作.
更新: 我现在阅读了大部分文章(相当多的阅读),除了付费书(需要更多时间).所有这些都是非常好的指针,并且更多作为适配器的Wcf的思考方式似乎是对问题2的一个很好的答案.如果我计划走这条路线,JGauffins对他的框架的工作也很有意思.
但是,如下面的一些评论所述,我觉得有些例子倾向于推荐或实施事件和/或命令源,消息总线等.对我来说,现在规划这种级别的扩展是不合适的.对于许多业务应用程序而言,这是一个"大型"(就内部应用程序而言,最多可以考虑几千个)处理大量数据的用户数量,而不是需要实现事件和命令的高度协作域队列经常与CQRS联系以应对.
根据下面的答案,我将开始的方法将基于上面的模型和这样的答案:
我只需要处理映射.Thoe pros胜过缺点.
我将应用程序服务拉回到基础架构并将Wcf视为"适配器"
我将使用命令对象并发送到应用程序服务.不使用域对象污染我的域.
为了降低复杂性,我尝试在没有事件/命令源,消息总线等的情况下进行管理.
在过去,我的项目已经变得难以管理,特别是当我不得不重新访问几年后重做或重新更新一个部分而不必重做所有内容.这一次,我正在努力使它变得简单,并且可以制作一个"可插拔"的应用程序设计,让我可以重新访问和重做一个部分,而无需触及所有内容.
我在想这个结构:

所以我想要的是能够在Bounded Context 2上工作,并在未来一个月/几年内使用许多新功能扩展它,同时保留Bounded Context 1.我还将在UI上工作,尤其是关于Bounded Context 2的部分.我还想让用户能够使用来自其他设备的有界上下文2.
优选地,即使在有界上下文2的UI中使用的web技术也将被更新,因为这是我们的主要关注领域并且被最多地使用,因此甚至可以将其置于其自己的用于web的UI项目中并且具有"登陆"提供管理用户等常用功能并允许用户登录的网站.
现在我正在考虑将所有这些分离到Visual Studio中的单独解决方案中以简化管理.但是我可以在一个解决方案中为每个解决方案创建一个文件夹并将所有内
我的问题是建议的方法是什么,在分离到不同的解决方案之前我应该考虑什么?
有没有如何管理这个的最佳实践?有经验的人有没有?
顺便说一句:由于这是由有界上下文划分的,因此需要在系统的各个部分之间进行通信,尽管没有直接依赖性(即上下文1管理和维护用于在上下文2中再次需要注册员工的业务逻辑).
更新 我意识到需要更多信息.
有两个有限的上下文.他们中没有一个真的像一个部门,即员工管理是当他们需要组织/存档与管理他人相关的信息时,上下文管理者所处理的,并且还获得关于重要事件的提醒.采购是员工在为部门购买货物并进行库存时所处的环境,可能有20-40个组织部门使用此.我正在考虑"报告"是否是一个单独的有界背景(尽管没有非常有趣的逻辑和行为).这些往往从小开始提供基本功能,随着时间的推移随着更多功能的增加而增长,人们"发现"新的需求.它们是单独更新的,我希望它们中的一些会及时发展成更大的系统,即使它们开始解决相当基本的需求.
c# architecture design-patterns domain-driven-design visual-studio
我使用Entity Framework并希望使用DDD原则.但是,有一些信息涉及在记录/持久性信息和域对象信息之间的边界线上的实体.
我的情况这些被放在一个抽象的基类中,所有实体都继承自:
public abstract class BaseEntity: IBaseEntity
{
/// <summary>
/// The unique identifier
/// </summary>
public int Id { get; set; }
/// <summary>
/// The user that created this instance
/// </summary>
public User CreatedBy { get; set; }
/// <summary>
/// The date and time the object was created
/// </summary>
public DateTime CreatedDate { get; set; }
/// <summary>
/// Which user was the last one to change this object
/// </summary>
public User LastChangedBy …Run Code Online (Sandbox Code Playgroud) c# design-patterns domain-driven-design entity-framework ef-code-first
我正在尝试实现 DDD 并且感觉我已经掌握了它的窍门,但我也遇到了一些问题。
在 90% 的域对象中,我想知道最后一个对其进行更改的用户。我不需要完整的审计跟踪——这对我的需求来说有点过分了。
我的所有类都实现一个抽象基类,其中包含:
public abstract class Base
{
public User LastChangedBy { get; set; }
public DateTime LastChangedDate { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
方案一:遵循 DDD 原则,但不那么优雅
永远不要让对象进入无效状态
public abstract class Base
{
public User LastChangedBy { get; protected set; }
public DateTime LastChangedDate { get; protected set; }
}
public SomeObject
{
.....
SomeBehaviorThatChangesObject(User changedBy, ...)
AnotherBehaviorThatChangesObject(User changedBy, ...)
}
Run Code Online (Sandbox Code Playgroud)
我必须将所有设置器设为私有,并将基类设置器设置为受保护。对对象所做的每个更改都需要通过一个以 (UserchangedBy) 作为参数的方法来完成。
非常安全,但是由于用户可能会对对象进行 6 次更改,因此我必须为每一个更改提供 User 对象。好吧,实际上,我必须为域模型中的几乎每个方法提供这个......
选项 2:不是最好的,但我读过相关内容
引入一个 bool IsValid 字段。在所有设置器中,我将 IsValid 设置为 …
我有一个消息接口:
public interface IMessage
{
int Id { get; set; }
string Body { get; set; }
string Title { get; set; }
Employee CreatedBy { get; set; }
MessageType MessageType { get; set; }
void Send(IEnumerable<User> recipients);
// or: void Send(User recipient);
}
Run Code Online (Sandbox Code Playgroud)
每个实现都必须具有发送消息的Send方法,但根据方案,Send方法不同.即如果它是私人消息,则只有一个收件人,并且还向该特定用户发送了另外的邮件通知,公共消息不会这样做.
该参数可以是用户列表或单个用户.我该如何实现呢?
我知道我可以为私人消息制作一个用户列表,但最好的情况是它是否像重载方法一样.
我在一个80%简单逻辑和20%复杂逻辑的项目上做TDD.如果certaing方法抛出错误并想知道正确的方法,我发现自己经常测试.我使用NUnit和JustMock.
我有两种方法可以做到这一点.使用ExpectedException属性,并指定类型.或写作如下.写下面的专业人员是我也可以断言exception.message(如果我已经自定义了),如果测试失败,我也会得到例外消息.但是我想和别人一起检查你是怎么做的.总结一下:
只是解释:供应商提供某些合同,部门接受一份合同但不能与同一供应商签订一份以上的合同(但是cource可以与不同的供应商签订不同的合同)
[Test]
public void Accepting_more_than_one_contract_from_supplier_throws_exception()
{
//Arrange
var department = new Department(Guid.NewGuid(), "1234");
var supplier = Mock.Create<Supplier>();
var contract1 = Mock.Create<DeliveryContract>();
var contract2 = Mock.Create<DeliveryContract>();
var id = Guid.NewGuid();
supplier.Arrange(x => x.Id).Returns(id);
contract1.Arrange(x => x.Supplier).Returns(supplier);
contract2.Arrange(x => x.Supplier).Returns(supplier);
//Act
department.AcceptContract(contract1);
//Assert
try
{
department.AcceptContract(contract2);
Assert.Fail("Duplicate contract with supplier did not throw an exception");
}
catch (Exception ex)
{
Assert.AreEqual(typeof(ArgumentException),ex.GetType(),ex.Message);
}
}
Run Code Online (Sandbox Code Playgroud)