口才配置

tech2022-09-03  124

口才配置

The Aggregate pattern is an important part of Domain Driven Design. It prevents inconsistencies and is responsible for enforcing business rules within a collection of objects. For these reasons alone, it is clear to see why it is a key component of a domain model.

聚合模式是域驱动设计的重要组成部分。 它防止不一致,并负责在对象集合中强制执行业务规则。 仅出于这些原因,就可以清楚地看到它为什么是域模型的关键组成部分。

Architectural advice recommends that the layer containing the Domain Model be independent from infrastructural concerns. While this is good advice, the Active Record pattern on the other hand wraps a row in the database. Because of this, it is almost impossible to decouple from the persistence layer.

体系结构建议建议包含域模型的层应与基础结构无关。 虽然这是一个很好的建议,但另一方面,Active Record模式在数据库中包装了一行。 因此,几乎不可能与持久层分离。

Mixing persistence concerns into a Domain Model can become complex and lead to a lot of bad decisions. This does not mean that it is impossible to create an Active Record Domain Model. In this article, we will work through an example of building an Aggregate which also extends Eloquent: a popular Active Record ORM.

将持久性关注点混入域模型可能会变得复杂,并导致许多错误的决策。 这并不意味着不可能创建活动记录域模型。 在本文中,我们将通过一个示例构建一个聚合,该聚合也扩展了Eloquent:一个流行的Active Record ORM。

什么是骨料? (What is an Aggregate?)

An Aggregate is a collection of objects which act as a single unit – with one of these objects acting as the Aggregate’s Root. Interaction from outside of the Aggregate must only communicate through the Root object. Aggregate Roots can then manage the consistency of all the objects within its boundary.

集合是作为单个单元的对象的集合–其中一个对象充当集合的根 。 来自聚合外部的交互只能通过Root对象进行通信。 然后,聚合根可以管理其边界内所有对象的一致性。

Boundaries for Aggregates define the scope of a transaction. Before the transaction can be committed, manipulation to the cluster of objects must comply with the business rules. Only one Aggregate can be committed within a single transaction. Any changes required to additional Aggregates must be eventually consistent, happening within another transaction.

聚合的边界定义了交易的范围。 在可以提交事务之前,对对象集群的操作必须符合业务规则。 同一笔交易中只能提交一个汇总。 最终,在其他事务中发生的对其他汇总所需的任何更改必须保持一致。

In his book, Implementing Domain-Driven Design, Vaughn Vernon outlines a set of guidelines in which he calls: “the rules of Aggregate design”:

沃恩·弗农(Vaughn Vernon)在他的《 实施域驱动设计》一书中概述了一套准则,他称之为:“总体设计规则”:

Protect True Invariants in Consistency Boundaries

保护一致性边界中的真不变量 Design Small Aggregates

设计小型骨料 Reference Other Aggregates Only By Identity

仅按身份引用其他汇总 Use Eventual Consistency Outside the Consistency Boundary

在一致性边界之外使用最终一致性

一个示例博客 (An Example Blog)

Since this is technically a blog post, it only seems fitting to use a Blog as our context. We will need a Post to have its own identity. A Post will need a Title along with Copy. Posts are written by an Author and can be commented on – but not if the Post has been locked.

由于从技术上讲这是一篇博客文章,因此将Blog用作我们的上下文似乎很合适。 我们将需要一个邮政来拥有自己的身份。 Post将需要Title和Copy 。 帖子由Author撰写,可以发表评论-但如果Post已被锁定,则不能发表评论。

Post is a good candidate as an Aggregate Root, with Title and Copy Value Objects. Author however will be on the outside of our boundary and only referenced by identity. But what about Comments?

Post是具有Title和Copy值对象的汇总根的良好候选者。 但是Author将在我们的边界之外,并且仅由身份引用。 但是Comment呢?

We will model Comment as an Entity within the Post Aggregate in our example. It is important to note however that clustering too many concepts within one Aggregate will result in large numbers of objects being hydrated. This can have an impact on performance, so take care and ensure you model Small Aggregates with clearly defined boundaries.

在我们的示例中,我们将Comment建模为Post Aggregate中的一个实体。 但是,重要的是要注意,在一个聚合中将太多概念聚类会导致大量对象水合。 这可能会对性能产生影响,因此请当心并确保您使用明确定义的边界对小型聚合进行建模。

Let’s take a look at what our Post aggregate may look like first without extending an ORM:

让我们先看一下Post聚合的外观,而无需扩展ORM:

final class Post { /** * @var PostId */ private $postId; /** * @var AuthorId */ private $authorId; /** * @var Title */ private $title; /** * @var Copy */ private $copy; /** * @var Lock */ private $locked; /** * @var array */ private $comments; /** * @param PostId $postId * @param AuthorId $authorId * @param Title $title * @param Copy $copy */ public function __construct(PostId $postId, AuthorId $authorId, Title $title, Copy $copy) { $this->postId = $postId; $this->authorId = $authorId; $this->title = $title; $this->copy = $copy; $this->locked = Lock::unlocked(); $this->comments = []; } public function lock() { $this->locked = Lock::locked(); } public function unlock() { $this->locked = Lock::unlocked(); } /** * @param Message $message */ public function comment(Message $message) { if ($this->locked->isLocked()) { throw new PostIsLocked; } $this->comments[] = new Comment( CommentId::generate(), $this->postId, $message ); } }

Here we have a simple class for our post. Within the constructor, we enforce invariants ensuring the object can not be created without essential information. Key information, such as title and copy, must be provided if a post object is to be created.

在这里,我们为帖子提供了一个简单的类。 在构造函数中,我们强制执行不变量,以确保没有必要的信息就无法创建对象。 如果要创建发布对象,则必须提供关键信息,例如标题和副本。

We also have behaviour for locking and unlocking a post – along with adding a comment. New comments are being prevented if the post is locked by throwing a PostIsLocked exception. This encapsulates the business rule within the Aggregate and makes it impossible to comment on locked posts.

我们还具有锁定和解锁帖子的行为-以及添加评论。 如果通过PostIsLocked异常锁定了帖子,则会阻止新评论。 这将业务规则封装在“聚合”内,并且无法对锁定的帖子发表评论。

口才介绍 (Introducing Eloquent)

As a starting point, we have been modelling our domain without extending an ORM. Let’s introduce Eloquent into our Post class and discuss the changes:

作为起点,我们一直在对我们的域建模而不扩展ORM。 让我们在我们的Post类中介绍Eloquent并讨论更改:

final class Post extends Eloquent { public function lock() { $this->locked = Lock::locked(); } public function unlock() { $this->locked = Lock::unlocked(); } /** * @param Message $message */ public function comment(Message $message) { if ($this->locked) { throw new PostIsLocked; } $comment = new Comment; $comment->postId = $this->postId; $comment->message = (string) $message; $this->comments->add($comment); } public function comments() { return $this->hasMany(Comment::class); } public function getLockedAttribute($value) { return Lock::fromString($value); } public function setLockedAttribute(Lock $lock) { $this->attributes['locked'] = $lock->asBool(); } }

Well, we’ve definitely written less code. Let’s take a closer look.

好吧,我们肯定写了更少的代码。 让我们仔细看看。

The first thing to notice is the removal of the class properties. Eloquent stores all the properties within a protected $attributes array on each class with magical __get() and __set() methods for access and manipulation.

首先要注意的是删除类属性。 __get()使用神奇的__get()和__set()方法将所有属性存储在每个类的受保护的$attributes数组中,以进行访问和操作。

We also no longer have a constructor. Eloquent hydrates the object’s protected $attributes array by passing in the row data through the constructor. Eloquent relies on this ability to provide several features such as loading relationships.

我们也不再有构造函数。 雄辩的通过将行数据通过构造函数传递来水化对象的受保护的$attributes数组。 Eloquent依靠这种能力来提供多种功能,例如加载关系。

Finally, we have added an additional method: comments(). Implementing this new method allows us to quickly make use of Eloquent’s Relationships. We could have called hasMany() internally within comment() without exposing a public method, but this would prevent eager loading if we decided to add an Active Record Query Repository.

最后,我们添加了一个附加方法: comments() 。 实施这种新方法使我们能够快速利用雄辩的关系 。 我们可以在comment() hasMany()内部内部调用hasMany()而不公开一个公共方法,但是如果我们决定添加Active Record Query Repository的话,这将防止急于加载。

属性 (Attributes)

Let’s talk about the removal of the class properties. Does this make a huge difference? The properties in our original class were private anyways – does storing them in a protected $attributes array really change much?

让我们谈谈删除类属性。 这有很大的不同吗? 无论如何,我们原始类中的属性还是私有的-将它们存储在受保护的$attributes数组中是否真的发生了很大变化?

Actually, this is probably the most significant change between a traditional object and an Active Record. Why? Because we are no longer dealing with an Object, we are dealing with a Data Structure. Objects expose behaviour, with data being hidden. Data Structures are exactly the opposite: they expose data and have no behaviour.

实际上,这可能是传统对象和活动记录之间最重大的变化。 为什么? 因为我们不再处理对象,所以我们正在处理数据结构。 对象暴露行为,而数据被隐藏。 数据结构恰恰相反:它们公开数据并且没有行为。

Eloquent provides data access with a public __get() magic method. Since we can access our data directly, it would be very easy to leach behavior which should be in our model. Remember the business rule that stated: “you can not comment on locked posts”? Imagine the following implementation:

__get()使用公共的__get()魔术方法提供数据访问。 由于我们可以直接访问数据,因此很容易提取模型中应该存在的行为。 还记得这样的业务规则:“您不能对锁定的帖子发表评论”吗? 想象以下实现:

if ($post->locked->isLocked()) { throw new PostIsLocked; } $post->comment($message);

This could easily lead to an Anemic Domain Model with little more than some “getters and setters”. Instead, apply the Tell-Dont-Ask principle and tell the model what it needs to do:

这很容易导致一个贫血域模型,而其中仅包含一些“获取者和设定者”。 相反,应用Tell-Dont-Ask原理并告诉模型它需要做什么:

try { $post->comment($message); } catch (PostIsLocked $e) { // Nope! }

Be careful not to strip your objects of behavior. Just because you can directly access and manipulate your data doesn’t mean that you should.

注意不要剥夺行为对象。 仅仅因为您可以直接访问和操纵数据并不意味着您应该这样做。

价值对象 (Value Objects)

Several additional methods were added to our Active Record version to allow the usage of Lock. Data within our object must cast to scalar types, but we can still use Value Objects if we take advantage of Eloquent’s accessor and mutator methods.

我们的Active Record版本中添加了一些其他方法,以允许使用Lock 。 对象中的数据必须转换为标量类型,但是如果我们利用Eloquent的accessor和mutator方法,仍然可以使用Value Objects。

public function getLockedAttribute($value) { return Lock::fromString($value); } public function setLockedAttribute(Lock $lock) { $this->attributes['locked'] = $lock->toBool(); }

As you can see, this adds additional public methods to an object focused on persistence mapping – instead of providing behavior. This isn’t typically something to worry too much about with Active Record, especially when you consider we are inheriting a base-class which already contains public methods such as save() and forceDelete(). You will become accustomed to this particular trade-off when modeling with Active Record.

如您所见,这向专注于持久性映射的对象添加了其他公共方法,而不是提供行为。 使用Active Record通常不必担心太多,尤其是当您考虑到我们继承一个已经包含诸如save()和forceDelete()类的公共方法的基类时。 使用Active Record建模时,您会习惯于这种特殊的权衡。

Where possible, you should take advantage of any ORM features which cast to and from Value Objects. Value Objects enforce their own invariants that can’t be invalid. The Lock class in our example is trivial, but imagine an Email object. You can be sure that instances of Email objects are valid. You may even wish to enforce additional business rules, such as ensuring the email can only belong to a particular domain.

在可能的情况下,您应该利用在值对象之间进行转换的任何ORM功能。 值对象强制执行自己的不变式,该不变式不能无效。 在我们的示例中, Lock类是微不足道的,但请想象一个Email对象。 您可以确保Email对象的实例有效。 您甚至可能希望强制执行其他业务规则,例如确保电子邮件只能属于特定域。

不变量 (Invariants)

An invariant is a rule which must always be valid within our domain. By definition, they are “constant throughout a certain range of conditions”. Imagine I were to write this article without a title – would it be valid? What about an author without a name? An alphabet without J and K?

不变是一个规则, 必须在我们的域内始终有效 。 根据定义,它们“ 在一定条件范围内是恒定的 ”。 想象一下,我写这篇没有标题的文章–它会有效吗? 没有名字的作者呢? 没有J和K的字母?

Violating an invariant would violate the essence of the concept. Aggregate Roots enforce invariants for itself and the cluster of objects within the Aggregate boundary. Entities also enforce invariants of their own, as do Value Objects, but only the Aggregate Root is responsible for enforcing rules for the cluster.

违反不变式将违反该概念的本质。 聚合根对其自身和聚合边界内的对象簇强制执行不变式。 实体也像值对象一样强制执行自己的不变量,但是只有聚合根负责执行群集的规则。

We can enforce invariants by using the class constructor and explicitly require arguments upon object instantiation. This would make it much harder for an object to be in a state which violates the concept. However, as we covered earlier, Eloquent requires the constructor to hydrate an object. This means we are unable to enforce invariants and protect our objects from being invalid when instantiated.

我们可以通过使用类构造函数来强制不变量,并在对象实例化时显式要求参数。 这将使对象更难进入违反概念的状态。 但是,正如我们之前介绍的,Eloquent要求构造函数将对象水合。 这意味着我们无法在实例化时强制执行不变式并保护我们的对象免于无效。

Udi Dahan suggest that we should never directly create Aggregate Roots and instead, use a factory method from another object to return our new instance:

Udi Dahan建议我们永远不要直接创建“聚合根” ,而应该使用另一个对象的工厂方法来返回我们的新实例:

final class Author extends Eloquent { // snip… /** * @param Title $title * @param Copy $copy * @return Post */ public function draftPost(Title $title, Copy $copy) { return new Post([ 'title' => $title, 'copy' => $copy, 'author_id' => $this->id ]); } }

In this example, we take Udi Dahan’s advice and use a factory method within another object to construct our Post. This allows us to use the language of the business to explain that an Author drafts a Post – but it also allows us to enforce invariants when we were otherwise unable to do so. Using this approach when modeling with Active Record requires softer skills to implement and enforce – since we cannot prevent direct object instantiation, the team must be aware of “the whats and hows” of creating Aggregates.

在此示例中,我们接受了Udi Dahan的建议,并在另一个对象中使用了工厂方法来构造Post。 这使我们能够使用业务语言来解释作者起草了文章,但也使我们能够以其他方式无法执行不变式。 在使用Active Record建模时使用这种方法需要更软的技能来实施和执行-由于我们无法防止直接实例化对象,因此团队必须意识到创建聚合的“内容和方式”。

Another alternative is to use a named constructor when creating a new instance:

另一种选择是在创建新实例时使用命名构造函数 :

final class Post { // snip… /** * @param AuthorId $authorId * @param Title $title * @param Copy $copy * @return static */ public static function draft(AuthorId $authorId, Title $title, Copy $copy) { return new Post([ 'title' => $title, 'copy' => $copy, 'author_id' => $authorId ]); } }

Just like our previous factory method, we use the language of the business to describe that a draft of a Post gets created. We are also able to type hint our Value Objects and enforce invariants.

就像我们以前的工厂方法一样,我们使用业务的语言来描述创建邮政草稿的过程。 我们还能够键入提示我们的值对象并强制执行不变式。

Again, this approach has a significant drawback when we consider inheriting from an Active Record ORM. Since we are extending Eloquent, there are already a number of static methods in the object graph. Not all domain concepts will have nice workflow titles such as “draft”. Often the most appropriate name for a method to describe creation is simply “create”. Unfortunately, Eloquent already has a static create() method.

同样,当我们考虑从Active Record ORM继承时,这种方法也有很大的缺点。 由于我们正在扩展Eloquent,因此对象图中已经存在许多静态方法。 并非所有领域概念都具有很好的工作流标题,例如“草稿”。 通常,用于描述创建方法的最合适的名称就是“创建”。 不幸的是,Eloquent已经有一个静态的create()方法。

There are other creational patterns which could help if separating the responsibility of object creation makes sense in your context.

如果将对象创建的责任分开在您的上下文中有意义,那么还有其他创建模式可能会有所帮助。

人际关系 (Relationships)

Aggregates allow us to treat a group of objects as a single unit. In our example, a post can have many comments. How might we go about coding this with Eloquent?

聚集使我们可以将一组对象视为一个单元。 在我们的示例中,帖子可以有很多评论。 我们如何使用Eloquent进行编码?

$post->comments()->save($comment);

Sure, this will get the job done, but there is a problem. By asking the post for its related comments, we completely bypass the Aggregate Root. All communication with an Aggregate must go through the Root so it can enforce the rules of the business. We were told by the business that “posts which are locked can not be commented on”, however we have just been able to save a new comment for a post without checking it’s locked status.

当然,可以完成工作,但是有问题。 通过询问帖子的相关评论,我们完全绕开了“聚合根”。 与聚合的所有通信都必须通过根,以便可以执行业务规则。 该业务部门告诉我们,“无法评论已锁定的帖子”,但是我们仅能保存帖子的新评论而无需检查其锁定状态。

Our example pushes this logic into the Post class, where it encapsulates the creation of Comment along with the check for the locked status:

我们的示例将此逻辑推入Post类,在其中封装了Comment的创建以及对锁定状态的检查:

$post->comment($message);

With this approach, we no longer leak persistence concerns. Code outside of the $post object does not need to explicitly call save() as it has already been invoked internally.

使用这种方法,我们不再需要泄漏持久性问题。 $post对象之外的代码不需要显式调用save()因为它已经在内部被调用。

结论 (Conclusion)

We’ve shown that modeling an Aggregate with Active Record is possible – but it is complex and has a lot of pitfalls. It can become quite messy if you try to treat an Active Record like a traditional object. Active Record is solely about the data whereas objects expose behavior and hide the data. We are fundamentally misusing the Active Record pattern – and the best we can do is to add behavior into what is essentially a data structure.

我们已经表明可以使用Active Record对聚合进行建模-但它很复杂并且存在很多陷阱。 如果您尝试将Active Record视为传统对象,则会变得非常混乱。 Active Record仅与数据有关,而对象则公开行为并隐藏数据。 我们从根本上是在滥用Active Record模式-我们能做的最好的就是将行为添加到本质上是数据结构中。

This article is not attempting to say that Active Record is bad, nor is it an appeal saying to always use it. It is simply a tool which we can use depending upon the situation. Active Record is a great tool when focusing on RAD, however in some situations it’s just not a good fit. I think, due to the amount of trade-offs discussed, modeling Aggregates fall under the latter.

本文并不是要说Active Record不好,也不是总是使用它的吸引力。 它只是一种可以根据情况使用的工具。 当专注于RAD时,Active Record是一个很好的工具,但是在某些情况下,它并不是一个很好的选择。 我认为,由于讨论了很多折衷,建模聚合属于后者。

Some development teams that are modeling domains may be happy to make the compromises needed to model Aggregates with Active Record. Others, however, will believe that it is never a good idea to have a model which can not be decoupled from infrastructure.

某些正在为域建模的开发团队可能会乐于做出使用Active Record建模聚合所需的折衷方案。 但是,其他人会相信拥有一个不能与基础架构脱钩的模型绝不是一个好主意。

Who is correct? The age-old answer to all questions: it depends.

谁是正确的? 对所有问题的古老答案:取决于情况。

翻译自: https://www.sitepoint.com/modeling-aggregate-eloquent/

口才配置

相关资源:一分钟口才训练_有效沟通的艺术.doc
最新回复(0)