让我们使用博客上下文的经典示例。在我们的域中,我们有以下几种情况:Users
可以写Posts
。 Posts
必须至少分类为一个Category
。可以使用Posts
来描述Tags
。 Users
可以对Posts
进行评论。
这四个实体(Post
,Category
,Tag
,Comment
)被实现为不同的聚合,因为我没有检测到任何规则,即实体数据应该干扰另一个实体。因此,对于每个聚合,我将有一个表示它的存储库。同样,每个集合通过其ID引用其他人。
[在CQRS之后,从这种情况下,我推导了由WriteNewPostCommand
,PublishPostCommand
,DeletePostCommand
等命令产生的典型用例以及它们各自的查询,以从存储库获取数据。 FindPostByIdQuery
,FindTagByTagNameQuery
,FindPostsByAuthorIdQuery
等...
取决于我们是应用程序的哪个站点(后端还是前端),我们将获得或多或少复杂的查询。因此,如果我们在首页上,则可能需要构建一些小部件来获取最新评论,类别的最新帖子等。涉及简单的Query
对象(很少的搜索条件)和QueryHandler
的查询简单(将单个存储库作为对处理程序类的依赖)
但是在其他地方,此查询可能更复杂。在管理面板中,我们需要在表中显示满足复杂搜索条件的关系。可能是有趣的搜索帖子,包括:作者名(无id),类别名,标签名,发布日期...属于不同集合和不同存储库的条件。
此外,在我们的帖子表中,我们不想显示帖子以及作者ID或类别ID。我们需要显示所有信息(名称用户,头像,类别名称,类别图标等)。
我的问题是:
在基础结构层,当我们设计存储库时,搜索方法(findAll,findById,findByCriterias ...)应该已经返回了引用所有关联ID的对应实体?我的意思是,如果具有方法findPostById(uuid)
或findPostByCustomFilter(filter)
,是否应该返回一个帖子实例,并引用该实例具有的所有类别ID,所有标签ID和作者ID?还是我的仓库应该有某种方法可以用我想要的关联填充给定的帖子实例?
如果我要搜索由约翰撰写的自2014年12月12日创建的帖子,并将其分类为“新闻”和“视频”类别以及标签“科幻”和“冒险”,并获取完整的详细信息每个集合,应如何创建我的Query
和QueryHandler
?
a)用我的所有参数(作者名,类别名,标签名,如果要完整检索详细的Query
,User
,Category
关联)创建一个Tag
,然后他的QueryHandler
对不同的读取模型进行编码在一个。或者...
b)创建其他Queries
(FindCategoryByName,FindTagByName,FindUserByName),然后我的Web控制器将其调用以供以后使用调用FindPostQuery,但现在将其他查询返回的authorid,categoryid,tagid传递给他?
b)解决方案看起来更干净,但似乎我更贵。
在查询方面,没有实体。您可以自由选择最适合自己需求的方式来填充读取的模型。无论您需要在屏幕(一部分)上显示什么数据,都将其放入读取模型中。返回这些读取模型的不是命令侧存储库,而是返回专用的查询侧数据访问对象。
您提到了“复杂的搜索条件”-我建议您使用相应的SearchCriteria
对象对其进行建模。该对象与技术无关,但是它将被传递到您的查询端数据访问对象,该对象将知道如何组合条件以针对其针对的特定数据存储构建较低级别的查询。
从CQRS,您将有一个分离的查询和命令堆栈。您的查询堆栈应代表项目中的不同模块,名称空间,dll或程序包。
a)您将创建一个QueryModel,此查询模型将返回所需的任何内容。如果您熟悉Entity Framework或NHibernate,则将创建一个Façade来保存此查询到gheter,DbContext或Session。
b)您可以创建此分离的查询,但是要再次说,如果您熟悉任何ORM,则应返回代表模型的集合,将每个集合返回为IQueryable并使用LET(Linq表达式树)创建查询堆栈更动态。
使用实体框架和C#作为示例:
public class QueryModelDatabase : DbContext, IQueryModelDatabase
{
public QueryModelDatabase() : base("dbname")
{
_products = base.Set<Product>();
_orders = base.Set<Order>();
}
private readonly DbSet<Order> _orders = null;
private readonly DbSet<Product> _products = null;
public IQueryable<Order> Orders
{
get { return this._orders.Include("Items").Include("Items.Product"); }
}
public IQueryable<Product> Products
{
get { return _products; }
}
}
然后您应该以所需的方式进行查询并返回任何内容:
using (var db = new QueryModelDatabase())
{
var queryable = from o in db.Orders.Include(p => p.Items).Include("Details.Product")
where o.OrderId == orderId
select new OrderFoundViewModel
{
Id = o.OrderId,
State = o.State.ToString(),
Total = o.Total,
OrderDate = o.Date,
Details = o.Items
};
try
{
var o = queryable.First();
return o;
}
catch (InvalidOperationException)
{
return new OrderFoundViewModel();
}
}
使用像这样的简单应用程序,更容易避免被聚合分散注意力。执行事件来源,通过一组易于查询所需方式的表来订阅事件。
换句话说,听起来您的主要目标是能够轻松查询所描述的场景。从最终目标开始。现在,编写事件处理程序以相应地调整表。
从事件和用户界面开始。然后,其他所有内容将很容易适应。 Google的“事件建模”将帮助您提出构想,以构建什么样的风格的应用程序。