• Command and Query Responsibility Segregation (CQRS) pattern


    Command and Query Responsibility Segregation (CQRS) pattern

     The Command and Query Responsibility Segregation (CQRS) pattern separates read and update operations for a data store. Implementing CQRS in your application can maximize its performance, scalability, and security. The flexibility created by migrating to CQRS allows a system to better evolve over time and prevents update commands from causing merge conflicts at the domain level.

    The problem

    In traditional architectures, the same data model is used to query and update a database. That's simple and works well for basic CRUD operations. In more complex applications, however, this approach can become unwieldy笨拙的;笨重的;不灵便的;难处理的. For example, on the read side, the application may perform many different queries, returning data transfer objects (DTOs) with different shapes. Object mapping can become complicated. On the write side, the model may implement complex validation and business logic. As a result, you can end up with an overly极度地 complex model that does too much.

    Read and write workloads are often asymmetrical非对称的, with very different performance and scale requirements.

    • There is often a mismatch between the read and write representations of the data, such as additional columns or properties that must be updated correctly even though they aren't required as part of an operation.

    • Data contention争夺 can occur when operations are performed in parallel on the same set of data.

    • The traditional approach can have a negative effect on performance due to load on the data store and data access layer, and the complexity of queries required to retrieve information.

    • Managing security and permissions can become complex, because each entity is subject to both read and write operations, which might expose data in the wrong context.

    Solution

    CQRS separates reads and writes into different models, using commands to update data, and queries to read data.

    • Commands should be task based, rather than data centric. ("Book hotel room", not "set ReservationStatus预约状态 to Reserved预约的").
    • Commands may be placed on a queue for asynchronous processing, rather than being processed synchronously.
    • Queries never modify the database. A query returns a DTO that does not encapsulate any domain knowledge.

    The models can then be isolated, as shown in the following diagram, although that's not an absolute requirement.

    Having separate query and update models simplifies the design and implementation. However, one disadvantage is that CQRS code can't automatically be generated from a database schema using scaffolding mechanisms such as O/RM tools.

    For greater isolation, you can physically separate the read data from the write data. In that case, the read database can use its own data schema that is optimized for queries. For example, it can store a materialized view of the data, in order to avoid complex joins or complex O/RM mappings. It might even use a different type of data store. For example, the write database might be relational, while the read database is a document database.

    If separate read and write databases are used, they must be kept in sync. Typically this is accomplished by having the write model publish an event whenever it updates the database. Updating the database and publishing the event must occur in a single transaction.

     

    The read store can be a read-only replica of the write store, or the read and write stores can have a different structure altogether. Using multiple read-only replicas can increase query performance, especially in distributed scenarios where read-only replicas are located close to the application instances.

    Separation of the read and write stores also allows each to be scaled appropriately to match the load. For example, read stores typically encounter a much higher load than write stores.

    Some implementations of CQRS use the Event Sourcing pattern. With this pattern, application state is stored as a sequence of events. Each event represents a set of changes to the data. The current state is constructed by replaying the events. In a CQRS context, one benefit of Event Sourcing is that the same events can be used to notify other components — in particular, to notify the read model. The read model uses the events to create a snapshot of the current state, which is more efficient for queries. However, Event Sourcing adds complexity to the design.

    Benefits of CQRS include:

    • Independent scaling. CQRS allows the read and write workloads to scale independently, and may result in fewer lock contentions.
    • Optimized data schemas. The read side can use a schema that is optimized for queries, while the write side uses a schema that is optimized for updates.
    • Security. It's easier to ensure that only the right domain entities are performing writes on the data.
    • Separation of concerns. Segregating the read and write sides can result in models that are more maintainable and flexible. Most of the complex business logic goes into the write model. The read model can be relatively simple.
    • Simpler queries. By storing a materialized view in the read database, the application can avoid complex joins when querying.

    Implementation issues and considerations

    Some challenges of implementing this pattern include:

    • Complexity. The basic idea of CQRS is simple. But it can lead to a more complex application design, especially if they include the Event Sourcing pattern.

    • Messaging. Although CQRS does not require messaging, it's common to use messaging to process commands and publish update events. In that case, the application must handle message failures or duplicate messages.

    • Eventual consistency. If you separate the read and write databases, the read data may be stale陈腐的;不新鲜的. The read model store must be updated to reflect changes to the write model store, and it can be difficult to detect when a user has issued a request based on stale read data.

    When to use this pattern

    Consider CQRS for the following scenarios:

    • Collaborative domains where many users access the same data in parallel. CQRS allows you to define commands with enough granularity粒度 to minimize merge conflicts at the domain level, and conflicts that do arise can be merged by the command.

    • Task-based user interfaces where users are guided through a complex process as a series of steps or with complex domain models. The write model has a full command-processing stack with business logic, input validation, and business validation. The write model may treat a set of associated objects as a single unit for data changes (an aggregate, in DDD terminology) and ensure that these objects are always in a consistent state. The read model has no business logic or validation stack, and just returns a DTO for use in a view model. The read model is eventually consistent with the write model.

    • Scenarios where performance of data reads must be fine tuned separately from performance of data writes, especially when the number of reads is much greater than the number of writes. In this scenario, you can scale out the read model, but run the write model on just a few instances. A small number of write model instances also helps to minimize the occurrence of merge conflicts.

    • Scenarios where one team of developers can focus on the complex domain model that is part of the write model, and another team can focus on the read model and the user interfaces.

    • Scenarios where the system is expected to evolve over time and might contain multiple versions of the model, or where business rules change regularly.

    • Integration with other systems, especially in combination with event sourcing, where the temporal failure of one subsystem shouldn't affect the availability of the others.

    This pattern isn't recommended when:

    • The domain or the business rules are simple.

    • A simple CRUD-style user interface and data access operations are sufficient.

    Consider applying CQRS to limited sections of your system where it will be most valuable.

    Event Sourcing and CQRS

    The CQRS pattern is often used along with the Event Sourcing pattern. CQRS-based systems use separate read and write data models, each tailored to relevant tasks and often located in physically separate stores. When used with the Event Sourcing pattern, the store of events is the write model, and is the official source of information. The read model of a CQRS-based system provides materialized views of the data, typically as highly denormalized views. These views are tailored to the interfaces and display requirements of the application, which helps to maximize both display and query performance.

    Using the stream of events as the write store, rather than the actual data at a point in time, avoids update conflicts on a single aggregate and maximizes performance and scalability. The events can be used to asynchronously generate materialized views of the data that are used to populate the read store.

    Because the event store is the official source of information, it is possible to delete the materialized views and replay all past events to create a new representation of the current state when the system evolves, or when the read model must change. The materialized views are in effect a durable耐用的,持久的 read-only cache of the data.

    When using CQRS combined with the Event Sourcing pattern, consider the following:

    • As with any system where the write and read stores are separate, systems based on this pattern are only eventually consistent. There will be some delay between the event being generated and the data store being updated.

    • The pattern adds complexity because code must be created to initiate and handle events, and assemble or update the appropriate views or objects required by queries or a read model. The complexity of the CQRS pattern when used with the Event Sourcing pattern can make a successful implementation more difficult, and requires a different approach to designing systems. However, event sourcing can make it easier to model the domain, and makes it easier to rebuild views or create new ones because the intent of the changes in the data is preserved.

    • Generating materialized views for use in the read model or projections of the data by replaying and handling the events for specific entities or collections of entities can require significant processing time and resource usage. This is especially true if it requires summation合计 or analysis of values over long periods, because all the associated events might need to be examined. Resolve this by implementing snapshots of the data at scheduled intervals, such as a total count of the number of a specific action that have occurred, or the current state of an entity.

    Example

    The following code shows some extracts from an example of a CQRS implementation that uses different definitions for the read and the write models. The model interfaces don't dictate any features of the underlying data stores, and they can evolve and be fine-tuned independently because these interfaces are separated.

    代码在原文里面 

    The following patterns and guidance are useful when implementing this pattern:

    • Data Consistency Primer. Explains the issues that are typically encountered due to eventual consistency between the read and write data stores when using the CQRS pattern, and how these issues can be resolved.

    • Data Partitioning Guidance. Describes best practices for dividing data into partitions that can be managed and accessed separately to improve scalability, reduce contention, and optimize performance.

    • Event Sourcing pattern. Describes in more detail how Event Sourcing can be used with the CQRS pattern to simplify tasks in complex domains while improving performance, scalability, and responsiveness. As well as how to provide consistency for transactional data while maintaining full audit trails and history that can enable compensating actions.

    • Materialized View pattern. The read model of a CQRS implementation can contain materialized views of the write model data, or the read model can be used to generate materialized views.

    • The patterns & practices guide CQRS Journey. In particular, Introducing the Command Query Responsibility Segregation pattern explores the pattern and when it's useful, and Epilogue: Lessons Learned helps you understand some of the issues that come up when using this pattern.

    • The post CQRS by Martin Fowler, which explains the basics of the pattern and links to other useful resources.

    eShopOnContainers 中的cqrs

    图片来源 eShopOnContainers 知多少[8]:Ordering microservice

    https://github.com/dotnet-architecture/eShopOnContainers/tree/dev/src/Services/Ordering/Ordering.API/Application/Commands

    https://github.com/dotnet-architecture/eShopOnContainers/tree/dev/src/Services/Ordering/Ordering.API/Application/Queries

    query操作是直接通过sql去操作的

     public async Task<Order> GetOrderAsync(int id)
            {
                using (var connection = new SqlConnection(_connectionString))
                {
                    connection.Open();
    
                    var result = await connection.QueryAsync<dynamic>(
                       @"select o.[Id] as ordernumber,o.OrderDate as date, o.Description as description,
                            o.Address_City as city, o.Address_Country as country, o.Address_State as state, o.Address_Street as street, o.Address_ZipCode as zipcode,
                            os.Name as status, 
                            oi.ProductName as productname, oi.Units as units, oi.UnitPrice as unitprice, oi.PictureUrl as pictureurl
                            FROM ordering.Orders o
                            LEFT JOIN ordering.Orderitems oi ON o.Id = oi.orderid 
                            LEFT JOIN ordering.orderstatus os on o.OrderStatusId = os.Id
                            WHERE o.Id=@id"
                            , new { id }
                        );
    
                    if (result.AsList().Count == 0)
                        throw new KeyNotFoundException();
    
                    return MapOrderItems(result);
                }
            }
    
            public async Task<IEnumerable<OrderSummary>> GetOrdersFromUserAsync(Guid userId)
            {
                using (var connection = new SqlConnection(_connectionString))
                {
                    connection.Open();
    
                    return await connection.QueryAsync<OrderSummary>(@"SELECT o.[Id] as ordernumber,o.[OrderDate] as [date],os.[Name] as [status], SUM(oi.units*oi.unitprice) as total
                         FROM [ordering].[Orders] o
                         LEFT JOIN[ordering].[orderitems] oi ON  o.Id = oi.orderid 
                         LEFT JOIN[ordering].[orderstatus] os on o.OrderStatusId = os.Id                     
                         LEFT JOIN[ordering].[buyers] ob on o.BuyerId = ob.Id
                         WHERE ob.IdentityGuid = @userId
                         GROUP BY o.[Id], o.[OrderDate], os.[Name] 
                         ORDER BY o.[Id]", new { userId });
                }
            }
  • 相关阅读:
    开源监控软件之争
    为什么很多公司都自主开发监控系统?
    为 UWP 应用提供的 .NET 网络 API
    亲,根据二八定律,你的监控工具可能白装了哦
    PHP7正式版测试,性能惊艳!
    Java Web 前端高性能优化(一)
    天下武功无坚不破,唯快不破!
    告警信息大爆炸,运维解放秘籍!
    第33节:Java面向对象中的异常
    第33节:Java面向对象中的异常
  • 原文地址:https://www.cnblogs.com/chucklu/p/13050607.html
Copyright © 2020-2023  润新知