未知数据源 2024年10月02日
Build a CQRS event store with Amazon DynamoDB
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文介绍了命令查询责任分离(CQRS)模式及事件溯源。CQRS源于命令-查询分离原则,在领域驱动设计社区中受到欢迎。使用事件溯源的CQRS架构将生成的事件存储在事件存储中,具有多种优势。文中还讨论了如何使用Amazon DynamoDB构建事件存储,并阐述了其优势。

🎯命令查询责任分离(CQRS)模式源于命令 - 查询分离原则,在领域驱动设计社区中流行。使用事件溯源的CQRS架构将事件存储在事件存储中,可避免使用昂贵的分布式事务,缓解对象 - 关系阻抗不匹配问题,存储所有数据变化的完整历史,用作审计日志并协助调试,支持使用专用非规范化视图的高性能查询。

💻CQRS的READ和WRITE模型可独立设计以满足应用需求。WRITE模型由处理命令的聚合组成,READ模型接收应用于实例化视图的查询以支持读取请求。当CQRS架构使用事件溯源时,聚合生成的数据以事件集合形式存储,事件按时间顺序加载以恢复聚合状态,并流向READ模型构建实例化视图以支持查询请求。

🛠️使用Amazon DynamoDB构建事件存储具有多种优势,如属于无服务器技术,无需管理服务器,可将更多时间用于编码业务逻辑和提升产品;具有NoSQL技术的性能优势,适合事件存储设计;原生支持变更数据捕获(CDC);其全球表功能适合构建分布式架构。

<p>The <a href="https://martinfowler.com/bliki/CQRS.html#:~:text=CQRS%20stands%20for%20Command%20Query,you%20use%20to%20read%20information.&quot; target="_blank" rel="noopener noreferrer">command query responsibility segregation (CQRS)</a> pattern, derived from the principle of <a href="https://en.wikipedia.org/wiki/Command%E2%80%93query_separation&quot; target="_blank" rel="noopener noreferrer">command-query separation</a>, has been popularized by the <a href="https://en.wikipedia.org/wiki/Domain-driven_design&quot; target="_blank" rel="noopener noreferrer">domain-driven design</a> community. CQRS architectures that use <a href="https://martinfowler.com/eaaDev/EventSourcing.html&quot; target="_blank" rel="noopener noreferrer">event sourcing</a> save generated events in an append-only log called an <a href="https://microservices.io/patterns/data/event-sourcing.html&quot; target="_blank" rel="noopener noreferrer">event store</a>. By using event sourcing, you can, among other benefits:</p><ul><li>Design applications to update a database and send events or messages without having to use expensive distributed transactions (also known as <a href="https://en.wikipedia.org/wiki/Two-phase_commit_protocol&quot; target="_blank" rel="noopener noreferrer">two-phase commit protocol</a> transactions) that span a database and a message broker.</li><li>Mitigate <a href="https://en.wikipedia.org/wiki/Object%E2%80%93relational_impedance_mismatch&quot; target="_blank" rel="noopener noreferrer">object-relational impedance mismatch</a> issues.</li><li>Store the full history of all data changes.</li><li>Use the event store as an audit log and to assist with debugging.</li><li>Support high-performance queries using dedicated denormalized views.</li></ul><p>Please note that the CQRS and event sourcing patterns are complex and should not be used for simple create, read, update, and delete style applications. Furthermore, implementers must allow for and handle <a href="https://en.wikipedia.org/wiki/Eventual_consistency&quot; target="_blank" rel="noopener noreferrer">eventual consistency</a>. However, CQRS and event sourcing may prove to be beneficial if you looking to build a system that:</p><ul><li>Supports a rich domain model that encapsulates complicated business logic and rules.</li><li>Is required to propagate events between microservices or over system boundaries.</li><li>Is designed with an event-first approach using methods like <a href="https://en.wikipedia.org/wiki/Event_storming&quot; target="_blank" rel="noopener noreferrer">event storming</a>.</li></ul><p>In this post, I discuss how to build an event store using <a href="https://aws.amazon.com/dynamodb/&quot; target="_blank" rel="noopener noreferrer">Amazon DynamoDB</a>.</p><h2>Before you get started</h2><p>This post assumes that you have a basic understanding of CQRS, event sourcing, and DynamoDB.</p><h2>Event sourcing and CQRS</h2><p>The diagram shown in Figure 1 that follows shows that a CQRS architecture includes both a READ and a WRITE model.</p><div id="attachment_14251" class="wp-caption aligncenter c5"><p><img class="aligncenter wp-image-24573 size-full c4" src="https://d2908q01vomqb2.cloudfront.net/887309d048beef83ad3eabf2a79a64a389ab1c9f/2022/09/19/DBBLOG-2257-Figure-1-CQRS-Arch.png&quot; alt="CQRS architecture includes both a READ and a WRITE model" width="760" height="325" /></p><p id="caption-attachment-13251" class="wp-caption-text">Figure 1 – CQRS READ and WRITE models with event sourcing</p></div><p>CQRS READ and WRITE models can be designed independently to meet an application’s requirements. The WRITE model consists of <a href="https://martinfowler.com/bliki/DDD_Aggregate.html&quot; target="_blank" rel="noopener noreferrer">aggregates</a> that process <a href="https://en.wikipedia.org/wiki/Command_pattern&quot; target="_blank" rel="noopener noreferrer">commands</a>. Commands represent an intent to modify the system. The READ Model receives <a href="https://martinfowler.com/eaaCatalog/queryObject.html&quot; target="_blank" rel="noopener noreferrer">queries</a> that are applied against materialized views and are used to support read requests.</p><p>An <a href="https://martinfowler.com/bliki/DDD_Aggregate.html&quot; target="_blank" rel="noopener noreferrer">aggregate</a> is composed of one or more component objects that encapsulate and enforce a domain’s business logic. When a CQRS architecture uses event sourcing, the data generated by an aggregate is stored as a collection of ordered <a href="https://martinfowler.com/articles/201701-event-driven.html&quot; target="_blank" rel="noopener noreferrer">events</a>. For example, assume there is a <code>Widget</code> aggregate that’s responsible for managing the domain of widgets where a widget is a small device being sold on a website. The <code>Widget</code> aggregate includes functionality for changing a widget’s name. A client system can request a widget name change by sending the <code>ChangeWidgetName</code> command to this aggregate. Upon successfully processing the command, the <code>Widget</code> aggregate yields its updated state in the form of the <code>WidgetNameChanged</code> event. This new event is then saved to the event store as a new record.</p><p>To restore its state, an aggregate loads its events from the event store in chronological order. The events are then streamed to the READ model of the architecture where denormalizing components use these events to build materialized views that support query requests.</p><h2>DynamoDB event store design</h2><p><a href="https://aws.amazon.com/dynamodb/&quot; target="_blank" rel="noopener noreferrer">DynamoDB</a> is a key-value NoSQL database that delivers single-digit-millisecond performance at any scale. It’s a fully managed, multi-Region, multi-active database with built-in security, backup and restore, and in-memory caching for internet-scale applications.</p><p><strong>Important:</strong> When working with DynamoDB, use the <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/best-practices.html&quot; target="_blank" rel="noopener noreferrer">best practices for designing and architecting with DynamoDB</a>. In DynamoDB, table schemas are flexible. Items with differing structure can be stored in the same table and only the primary key for the table and secondary indexes is enforced. To support item uniqueness, you can prefix different values to the <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-sort-keys.html&quot; target="_blank" rel="noopener noreferrer">partition key or sort keys</a> if you’re using a composite primary key. Understanding the access patterns for your application is important to the success of your DynamoDB implementation.</p><p>An event store can be implemented using traditional relational database tables. However, by using DynamoDB you gain a number of advantages over this approach including:</p><ul><li>DynamoDB belongs to the <a href="https://aws.amazon.com/serverless/&quot; target="_blank" rel="noopener noreferrer">serverless</a> technology family which means you don’t have to manage servers or spend significant amounts of time on infrastructure management. This means you can spend more time on coding business logic and enhancing your product.</li><li>You gain <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SQLtoNoSQL.WhyDynamoDB.html&quot; target="_blank" rel="noopener noreferrer">performance benefits</a> associated with NoSQL technology. An event store design is not characterized by joins between tables or relations which makes it an ideal candidate for DynamoDB.</li><li>DynamoDB supports change data capture (CDC) natively using <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.html&quot; target="_blank" rel="noopener noreferrer">Amazon DynamoDB Streams</a> which is discussed in detail later in this post. Adding CDC to a relational database can be a non-trivial exercise and may involve purchasing additional tools.</li><li>The DynamoDB <a href="https://aws.amazon.com/dynamodb/global-tables/&quot; target="_blank" rel="noopener noreferrer">global tables</a> feature makes it an excellent choice for building distributed architectures where you can write to different <a href="https://docs.aws.amazon.com/index.html&quot; target="_blank" rel="noopener noreferrer">Amazon Web Services (AWS)</a> Regions and let the service coordinate the data replication and conflict resolution for you.</li></ul><p>For the event store, as shown in Figure 2 that follows, there are three entity types:</p><ul><li>Aggregate: A CQRS pattern that encapsulates the implementation of business logic.</li><li>Event: An event represents something that has occurred.</li><li>Snapshot: A snapshot represents the derived state of existing events at a certain point in time. A snapshot is used so that the full history of events doesn’t need to be stored or loaded at runtime.</li></ul><div id="attachment_14252" class="wp-caption aligncenter c5"><p><img class="aligncenter wp-image-24574 size-full c4" src="https://d2908q01vomqb2.cloudfront.net/887309d048beef83ad3eabf2a79a64a389ab1c9f/2022/09/19/DBBLOG-2257-Figure-2-ERD.png&quot; alt="Entity types" width="281" height="113" /></p><p id="caption-attachment-13252" class="wp-caption-text">Figure 2 – Event store entity relationship diagram</p></div><p>In this design, each entity type is read using its own access patterns, so the model includes a table per entity-type. This lets you optimize the primary key and data type for efficiency in storage and throughput, and also supports table-level choices such as backups, exports, periodic scans, storage class, and capacity mode based on the differing requirements for each entity type and its access patterns.</p><h2>Event table</h2><p>Assume that there is a <code>Widget</code> aggregate with the identifier (ID) of <code>123</code>. Using this example, Figure 3 that follows shows how, over time, this aggregate’s state is stored as a series of events to the event table.</p><div id="attachment_14253" class="wp-caption aligncenter c5"><p><img class="alignnone size-full wp-image-24575 aligncenter c6" src="https://d2908q01vomqb2.cloudfront.net/887309d048beef83ad3eabf2a79a64a389ab1c9f/2022/09/19/DBBLOG-2257-Figure-3-Event.png&quot; alt="Aggregate state is stored as a series of events to the event table" width="1159" height="301" /></p><p id="caption-attachment-13253" class="wp-caption-text">Figure 3 – DynamoDB event table</p></div><p>In DynamoDB, <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html&quot; target="_blank" rel="noopener noreferrer">items</a> are equivalent to rows in a relational database and attributes are comparable to fields. The event table in the preceding Figure 3 includes three events:</p><ul><li><code>WidgetCreated</code>, which has partition key (PK) = <code>123</code> and sort key (SK) = <code>1</code>.</li><li><code>WidgetNameChanged</code>, which has PK = <code>123</code> and SK = <code>2</code>.</li><li><code>WidgetDescriptionChanged</code>, has PK = <code>123</code> and SK = <code>3</code>.</li></ul><p>The design featured in the event table uses a composite primary key that consists of both a partition key attribute as a number and a sort key attribute as a number. When the sort key is a number, you can issue <a href="https://aws.amazon.com/blogs/database/using-sort-keys-to-organize-data-in-amazon-dynamodb/&quot; target="_blank" rel="noopener noreferrer">range queries</a> and sort the events based on the event’s number, which increases chronologically. When using strings as <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes&quot; target="_blank" rel="noopener noreferrer">sort keys</a>, DynamoDB collates and compares strings using the bytes of the underlying UTF-8 string encoding.</p><p><strong>Note</strong>: A production-ready CQRS solution requires more attribute values but for the purpose of describing the event store design, the attributes listed will suffice.</p><p>Event payloads are stored in the <code>payload</code> attribute and are serialized to JSON. However, because the <code>payload</code> attribute is specified as a string, you can use a different serialization format for this value (for example <a href="https://en.wikipedia.org/wiki/Protocol_Buffers&quot; target="_blank" rel="noopener noreferrer">protocol buffers</a>).</p><p>The <code>sort key</code> value for events represents the sequence in which these entities are created. This value is unique within the context of the owning aggregate and is managed by the aggregate itself which tracks event counts and order.</p><p>Event items should be immutable as they represent a fact. To protect them, you can create identity-based access policies to control access to <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_dynamodb_specific-table.html&quot; target="_blank" rel="noopener noreferrer">specific tables</a> and <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_dynamodb_items.html&quot; target="_blank" rel="noopener noreferrer">items</a>.</p><h2>Aggregate table</h2><p>Figure 4 that follows depicts the design of the aggregate table with one example: the primary aggregate item <code>Widget</code>, which has PK = <code>123</code>.</p><div id="attachment_14254" class="wp-caption aligncenter c5"><p><img class="alignnone size-full wp-image-24576 aligncenter c6" src="https://d2908q01vomqb2.cloudfront.net/887309d048beef83ad3eabf2a79a64a389ab1c9f/2022/09/19/DBBLOG-2257-Figure-4-Aggregate.png&quot; alt="DynamoDB aggregate table" width="863" height="149" /></p><p id="caption-attachment-13254" class="wp-caption-text">Figure 4 – DynamoDB aggregate table</p></div><p>For the aggregate item <code>123</code>, only one entry is required; the main item, which describes a <code>Widget</code> aggregate. The item includes a <code>last_events</code> attribute, which contains a sorted map of the last set of events generated by the <code>Widget</code> aggregate. In this example, the last time aggregate <code>123</code> was invoked by a command , event <code>4</code> <code>WidgetStockUpdated</code> was created but this isn’t recorded as a separate event item at this stage. This event will eventually be created as an individual item in the event table as part of a subsequent step, which is discussed in the later section: <em>Loading an aggregate algorithm.</em> The other attribute of interest is <code>version</code>. The main aggregate item <code>123</code> has a version value of <code>2</code>, which means it’s been updated twice since its creation. The <code>version</code> attribute value is initially set to <code>0</code>. DynamoDB supports <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBMapper.OptimisticLocking.html&quot; target="_blank" rel="noopener noreferrer">optimistic locking</a> through the use of a version attribute. This is a concurrency-control mechanism that you can use to handle <a href="https://en.wikipedia.org/wiki/Race_condition&quot; target="_blank" rel="noopener noreferrer">race conditions</a>. This uses the functionality of <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ConditionExpressions.html&quot; target="_blank" rel="noopener noreferrer">DynamoDB condition expressions</a>.</p><p><strong>Important</strong>: For production systems, ensure that your own aggregate ID partition key values are <a href="https://aws.amazon.com/blogs/database/choosing-the-right-dynamodb-partition-key/&quot; target="_blank" rel="noopener noreferrer">unique and support the even distribution of data</a>.</p><h2>Snapshot table</h2><p>Figure 5 shows the design of the snapshot table which includes the snapshot, representing all events up to and including event <code>2</code> which has PK = <code>123</code> and SK = <code>1</code>.</p><div id="attachment_14255" class="wp-caption aligncenter c5"><p><img class="alignnone size-full wp-image-24577 aligncenter c6" src="https://d2908q01vomqb2.cloudfront.net/887309d048beef83ad3eabf2a79a64a389ab1c9f/2022/09/19/DBBLOG-2257-Figure-5-Snapshot.png&quot; alt="Design of the snapshot table which includes the snapshot" width="1159" height="149" /></p><p id="caption-attachment-13255" class="wp-caption-text">Figure 5 – DynamoDB Snapshot table</p></div><p>The <code>Sort key</code> value for snapshots, like events, represents the sequence in which snapshot items are created. This value is unique within the context of the owning aggregate and is managed by the aggregate itself which tracks snapshot counts and order. The <code>event_number</code> attribute for a snapshot is the number of the last event that was processed at the time the snapshot was taken.</p><p><strong>Note</strong>: A production-ready CQRS solution might only take a snapshot after tens, hundreds, or even thousands of events have been created since the last time a snapshot was taken.</p><h2>Expiring event and snapshot items with Time to Live attributes</h2><p>Once an event or snapshot has been superseded by a newer snapshot, <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html&quot; target="_blank" rel="noopener noreferrer">DynamoDB Time to Live (TTL)</a> attributes are added to the older items. Using TTL, the size of the event and snapshot tables is minimized as DynamoDB cleans and removes items marked with expiry timestamps. However, the items can be archived prior to the expiry event taking place. When a snapshot item is created, the TTL can be set by using <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.html&quot; target="_blank" rel="noopener noreferrer">Amazon DynamoDB Streams</a> and an <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.Lambda.html&quot; target="_blank" rel="noopener noreferrer">AWS Lambda trigger</a>.</p><p>Figure 6 that follows shows how a <a href="https://aws.amazon.com/lambda/&quot; target="_blank" rel="noopener noreferrer">Lambda</a> function, initiated by DynamoDB Streams, can be used to set the TTL attribute on event items in the event table as a result of a snapshot item being written to the snapshot table.</p><div id="attachment_14256" class="wp-caption aligncenter c5"><p><img class="alignnone size-full wp-image-24578 aligncenter c4" src="https://d2908q01vomqb2.cloudfront.net/887309d048beef83ad3eabf2a79a64a389ab1c9f/2022/09/19/DBBLOG-2257-Figure-6-TTLTrigger.png&quot; alt="shows how a Lambda function, initiated by DynamoDB Streams, can be used to set the TTL attribute on event items in the event table as a result of a snapshot item being written to the snapshot table" width="751" height="129" /></p><p class="wp-caption-text">Figure 6 – DynamoDB Streams Lambda function</p></div><p><strong>Note</strong>: Items deleted using TTL can also be <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/time-to-live-ttl-streams.html&quot; target="_blank" rel="noopener noreferrer">processed with DynamoDB Streams</a>. DynamoDB attempts to clean items within 48 hours of the TTL value.</p><h2>Loading an aggregate algorithm</h2><p>An aggregate’s state is loaded or restored when it receives a command for processing. The command carries the ID of the aggregate (in the example provided, this is <code>123</code>).</p><p>This load aggregate algorithm uses <a href="http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/QueryAndScan.html&quot; target="_blank" rel="noopener noreferrer">queries</a> that apply key condition expressions, where the aggregate ID is used as a partition key to scope selective reads within the associated item collection. Since we’re using sort keys, the most recent snapshot is returned by setting the <code>ScanIndexForward</code> parameter to <code>false</code> and <code>Limit</code> to <code>1</code> and new events can be loaded through the use of <a href="https://aws.amazon.com/blogs/database/using-sort-keys-to-organize-data-in-amazon-dynamodb/&quot; target="_blank" rel="noopener noreferrer">range functions</a> like between, greater than or equal to, and less than or equal to.</p><p><strong>Note</strong>: If the command being processed creates a new aggregate, the loading process described in the preceding paragraph isn’t used.</p><p>The steps for restoring an aggregate’s state are:</p><ol><li>Load the primary aggregate item.</li><li>Prepare existing aggregate state (preparation phase).</li><li>Load the last snapshot (if present).</li><li>Load the remaining events.</li></ol><p>Figure 7 that follows shows the sequence of steps for loading an aggregate’s state from the event store tables. The diagram also depicts the <a href="https://martinfowler.com/eaaCatalog/serviceLayer.html&quot; target="_blank" rel="noopener noreferrer">service</a> and <a href="https://martinfowler.com/eaaCatalog/repository.html&quot; target="_blank" rel="noopener noreferrer">repository</a> application layers and the interactions between those layers.</p><div id="attachment_14257" class="wp-caption aligncenter c5"><p><img class="alignnone size-full wp-image-24579 aligncenter c4" src="https://d2908q01vomqb2.cloudfront.net/887309d048beef83ad3eabf2a79a64a389ab1c9f/2022/09/19/DBBLOG-2257-Figure-7-LoadAggregateSequence.png&quot; alt="The sequence of steps for loading an aggregate’s state from the event store tables" width="954" height="494" /></p><p id="caption-attachment-13257" class="wp-caption-text">Figure 7 – Load aggregate sequence diagram</p></div><p>The steps to load an aggregate’s state, depicted in Figure 7, are as follows:</p><ol><li><em>Load the primary aggregate item</em> – includes using the <a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html&quot; target="_blank" rel="noopener noreferrer">GetItem</a> operation to retrieve the relevant aggregate item from the aggregate table using the received identifier. If the operation finds no matched item, then return an error to the client.</li><li><em>Prepare existing aggregate state –</em> consists of the event data preparation phase. The events that were created the last time the aggregate was saved are now persisted as separate items in the event table. A <code>PutItem</code> operation is made for each event in the <code>last_events</code> attribute. If an event already exists in the event table, the event doesn’t need to be persisted as events are immutable. Generally, only one event will be present in the map so this doesn’t represent a significant overhead. This is an important step as it will guarantee event and aggregate data is in the correct state before the application of business logic.</li></ol><p><strong>Important</strong>: The <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Limits.html&quot; target="_blank" rel="noopener noreferrer">maximum item size in DynamoDB</a> is 400 KB so it’s important to save the events as separate items to ensure the aggregate item size remains under this limit.</p><ol start="3"><li><em>Load the last snapshot</em> – starts by determining if the aggregate contains a snapshot. To do this, issue a query to DynamoDB to find the most recent snapshot in the Snapshot table. As part of this query, reverse sort on <code>Sort key</code> by setting <code>ScanIndexForward</code> to <code>false</code> and <code>Limit</code> to <code>1</code>. This will make certain only the latest snapshot item is returned. A snapshot item contains an <code>event_number</code> attribute which can be used to determine which events to load in the next step. This is used to ensure that only those events that have occurred since the snapshot was taken will be retrieved from the event table.</li><li><em>Load the remaining items</em> – restores the aggregate state by loading all relevant events. These are the events that have been created since the last snapshot was taken or all events if no snapshots are available. You can query for events using <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html&quot; target="_blank" rel="noopener noreferrer">range key queries</a>. To do this, query for events items in the event table using <code>Sort key &gt;= <em>n</em></code> where <code><em>n</em></code> is the number of the earliest event that needs to retrieved.</li></ol><p>With the completion of step 4, the aggregate and its data are fully loaded into memory and the current received command can be processed.</p><h2>Saving an aggregate algorithm</h2><p>An aggregate is saved after it processes a command successfully. The aggregate will emit events and optionally a snapshot.</p><p>To save an aggregate’s state to the event store:</p><ol><li>Save the primary aggregate item.</li><li>Save the snapshot Item (optional).</li></ol><p><strong>Note</strong>: A precondition for step 1 is that if the aggregate already exists, its state has been fully loaded into memory. This doesn’t apply to new aggregates. Figure 8 that follows shows the sequence of steps for saving an aggregate to the event store tables.</p><div id="attachment_14258" class="wp-caption aligncenter c5"><p><img class="alignnone size-full wp-image-24572 aligncenter c4" src="https://d2908q01vomqb2.cloudfront.net/887309d048beef83ad3eabf2a79a64a389ab1c9f/2022/09/19/DBBLOG-2257-Figure-8-SaveAggregateSequence.png&quot; alt="The steps to save an aggregate" width="953" height="402" /></p><p id="caption-attachment-13258" class="wp-caption-text">Figure 8 – Save aggregate sequence diagram</p></div><p>The steps to save an aggregate, depicted in Figure 8, are as follows:</p><ol><li><em>Save the primary aggregate item</em> consists of a single <a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html&quot; target="_blank" rel="noopener noreferrer">PutItem</a> operation against the aggregate table. This operation uses the <code>version</code> attribute to ensure existing data is not incorrectly overwritten. DynamoDB supports using <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ConditionExpressions.html&quot; target="_blank" rel="noopener noreferrer">condition expressions</a> with single item atomic writes. <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-apis.html&quot; target="_blank" rel="noopener noreferrer">Transactions</a> are recommended when you have more than one item to modify. In this example, the design only requires a single item write as part of the aggregate save process for step 1. The <code>PutItem</code> operation for the aggregate item includes a sorted map (the <code>last_events</code> attribute) of the events generated by the aggregate in response to processing the current command. The events in the map will be saved as individual items as part of the preparation phase when an aggregate is loaded. This is described in the section <em>Loading an aggregate algorithm</em>.</li><li><em>Save the snapshot Item</em> is an optional step that’s run if the aggregate emits a snapshot in addition to the events. Saving a snapshot requires an additional <code>PutItem</code> operation, which is applied against the Snapshot table.</li></ol><h2>Loading and saving aggregate examples</h2><p>This section provides examples that show how the event store tables are updated when an aggregate is saved and loaded. The save and load processes are initiated when an aggregate processes a command.</p><h3>CreateWidget command</h3><p>As shown in Figure 9 that follows, a <code>Widget</code> aggregate item is created in the aggregate table after the successful processing of the <code>CreateWidget</code> command.</p><div id="attachment_14259" class="wp-caption aligncenter c5"><p><img class="alignnone size-full wp-image-24580 aligncenter c6" src="https://d2908q01vomqb2.cloudfront.net/887309d048beef83ad3eabf2a79a64a389ab1c9f/2022/09/19/DBBLOG-2257-Figure-9-AggregateCreateWidget.png&quot; alt="A Widget aggregate item is created in the aggregate table after the successful processing of the CreateWidget command" width="1159" height="149" /></p><p id="caption-attachment-13259" class="wp-caption-text">Figure 9 – CreateWidget command updates to the aggregate table</p></div><h3>ChangeWidgetName command</h3><p>Figure 10 that follows shows the updates to the event table when a <code>ChangeWidgetName</code> command is received for processing. The <code>WidgetCreated</code> event saved previously is created as a new item in the event table.</p><div id="attachment_142510" class="wp-caption aligncenter c5"><p><img class="alignnone size-full wp-image-24581 c6" src="https://d2908q01vomqb2.cloudfront.net/887309d048beef83ad3eabf2a79a64a389ab1c9f/2022/09/19/DBBLOG-2257-Figure-10-EventChangeWidgetName.png&quot; alt="Updates to the event table when a ChangeWidgetName command is received for processing" width="1159" height="149" /></p><p id="caption-attachment-132510" class="wp-caption-text">Figure 10 – CreateWidget command pre-processing updates to the event table</p></div><p>Figure 11 that follows shows the updates to the aggregate item in the aggregate table after the aggregate processes a <code>ChangeWidgetName</code> command.</p><div id="attachment_142511" class="wp-caption aligncenter c5"><p><img class="alignnone size-full wp-image-24582 aligncenter c6" src="https://d2908q01vomqb2.cloudfront.net/887309d048beef83ad3eabf2a79a64a389ab1c9f/2022/09/19/DBBLOG-2257-Figure-11-AggregateChangeWidgetName.png&quot; alt="Updates to the aggregate item in the aggregate table after the aggregate processes a ChangeWidgetName command" width="1159" height="149" /></p><p id="caption-attachment-132511" class="wp-caption-text">Figure 11 – ChangeWidgetName command updates to the aggregate table</p></div><h3>ChangeWidgetDescription command</h3><p>Figure 12 that follows shows the updates to the event table when a <code>ChangeWidgetDescription</code> command is received for processing. The <code>WidgetNameChanged</code> event saved previously is created as a new item in the event table.</p><div id="attachment_142512" class="wp-caption aligncenter c5"><p><img class="alignnone size-full wp-image-24583 aligncenter c6" src="https://d2908q01vomqb2.cloudfront.net/887309d048beef83ad3eabf2a79a64a389ab1c9f/2022/09/19/DBBLOG-2257-Figure-12-EventChangeWidgetDescription.png&quot; alt="Updates to the event table when a ChangeWidgetDescription command is received for processing" width="1172" height="225" /></p><p id="caption-attachment-132512" class="wp-caption-text">Figure 12 – ChangeWidgetDescription command pre-processing updates to the event table</p></div><p>Figure 13 that follows shows the updates to the aggregate item in the aggregate table after the aggregate processes a <code>ChangeWidgetDescription</code> command.</p><div id="attachment_142513" class="wp-caption aligncenter c5"><p><img class="alignnone size-full wp-image-24584 aligncenter c6" src="https://d2908q01vomqb2.cloudfront.net/887309d048beef83ad3eabf2a79a64a389ab1c9f/2022/09/19/DBBLOG-2257-Figure-13-AggregateChangeWidgetDescription.png&quot; alt="Updates to the aggregate item in the aggregate table after the aggregate processes a ChangeWidgetDescription command" width="1172" height="149" /></p><p id="caption-attachment-132513" class="wp-caption-text">Figure 13 – ChangeWidgetDescription command updates to the aggregate table</p></div><h2>Event store change data capture with DynamoDB Streams</h2><p>All event table changes can be made available through the use of <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.html&quot; target="_blank" rel="noopener noreferrer">DynamoDB Streams</a>, a message bus that persists entries for 24 hours. DynamoDB Streams stores every modification made to a table and delivers the associated items exactly once, in order (by item). Applications can read from this change log and propagate events to one or more subscribers.</p><h2>Conclusion</h2><p>If your organization is data-driven, you need to ensure that your designs support data insights now and in the future. By building CQRS and event sourcing solutions on AWS with DynamoDB at their core, you can make significant progress towards future-proofing your data platforms.</p><p>In this post, you received an overview of how to build an event store using DynamoDB. You saw the basic steps required to implement the event store using DynamoDB features, including:</p><ul><li>TTL attributes.</li><li>Optimistic locking and condition expressions.</li><li>DynamoDB Streams for change data capture.</li><li>Maximizing the efficiency of DynamoDB queries with partition and sort keys.</li></ul><p>AWS provides services that you can use to build other components of a CQRS architecture, including:</p><p>For more information, see:</p><ul><li>Martin Fowler’s articles at <a href="http://martinfowler.com/&quot; target="_blank" rel="noopener noreferrer">martinFowler.com</a> about <a href="https://martinfowler.com/eaaDev/EventSourcing.html&quot; target="_blank" rel="noopener noreferrer">event sourcing</a> and <a href="https://martinfowler.com/bliki/CQRS.html&quot; target="_blank" rel="noopener noreferrer">CQRS</a>.</li><li>Chris Richardson’s articles at <a href="http://microservices.io&quot; target="_blank" rel="noopener noreferrer">Microservices.io</a> about <a href="https://microservices.io/patterns/data/event-sourcing.html&quot; target="_blank" rel="noopener noreferrer">event sourcing</a> and <a href="https://microservices.io/patterns/data/cqrs.html&quot; target="_blank" rel="noopener noreferrer">CQRS</a>.</li></ul><h3>About the author</h3><p><a href="https://d2908q01vomqb2.cloudfront.net/887309d048beef83ad3eabf2a79a64a389ab1c9f/2022/09/19/LukePhoto-Resize.jpg&quot;&gt;&lt;img class="size-full wp-image-24596 alignleft" src="https://d2908q01vomqb2.cloudfront.net/887309d048beef83ad3eabf2a79a64a389ab1c9f/2022/09/19/LukePhoto-Resize.jpg&quot; alt="Luke Popplewell" width="100" height="111" /></a><strong>Luke Popplewell</strong> works primarily with federal entities in the Australian Government. In his role as an architect, Luke uses his knowledge and experience to help organizations reach their goals on the AWS cloud. Luke has a keen interest in serverless technology, digital innovation, modernization, DevOps, and event-driven architectures.</p>

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

命令查询责任分离 事件溯源 Amazon DynamoDB 技术优势
相关文章