Architecture|Three Problems in Microservice Business Development - Split, Transaction, Query (Transfer)

Reprinted from: http://mt.sohu.com/it/d20170517/141317853_470018.shtml

 

Architecture|Three Problems in Microservice Business Development - Split, Transaction, Query (Part 1)

 

  Microservice architecture is becoming more and more popular. It's an approach to modularity. It splits an entire application into individual services. It enables teams to deliver high-quality software faster when developing large and complex applications. Team members can easily embrace new technologies because they can use the latest and recommended technology stack to implement their respective services. Microservices architecture also improves application scalability by allowing each service to be deployed on optimal hardware.

  But microservices are not everything. Especially in the areas of domain model, transaction and query, it always seems to be unable to adapt to splitting. In other words, these pieces are also places where microservices need to be dealt with specifically, compared to the monolithic architecture of the past.

  In this post, I will describe an approach to developing microservices that solves these problems. Mainly through the domain model design, that is, DDD and event sourcing (Event Sourcing) and CQRS. Let's first look at the problems developers face when developing microservices.

  Challenges in Microservice Development

  Modularity is very necessary when developing large and complex applications.

  Many applications are now too large for one person to complete. And it's so complicated that it's impossible for one person to understand.

  In this case, the application must be split into individual modules. In a monolithic application, a module is defined as, for example, a java package. However, this approach is not very ideal in practice, and over time, the monolithic application becomes larger and larger. A microservice architecture treats a service as a modular unit.

  Each service corresponds to a business capability that an organization needs to create value. For example, a microservice-based online store includes various services, including Order Service, Customer Service, and Catalog Service.

  

  Every service has an impenetrable and hard-to-violate boundary. That is, each microservice should provide a separate and independent capability. This way, the modularity of the application is easier to preserve over time.

  There are other advantages to the microservices architecture. Including the ability to deploy services independently, expand services independently, and so on. compared to a single unit.

  Unfortunately, splitting is not as easy as it sounds. quite difficult.

  The application domain model, transaction, and query are the splitting problems you face during and after splitting. Let's take a look at the specific reasons.

  Problem 1 - Splitting the Domain Model

  The Domain Model pattern is a very good way to implement complex business logic. For example, for an online store, the domain model would contain several classes: Order, OrderLineItem, Customer and Product. In the microservice architecture, the Order and OrderLineItem classes are part of the Order Service; Customer is part of the Customer Service; Product is part of the Catalog Service.

  

  )

  One of the challenges of splitting the domain model is that classes often reference one or more other classes.

  For example, the Order class refers to the customer Customer of the order; OrderLineItem refers to the Product ordered by the order.

  What do we do with these references that want to cross service boundaries?

  Later you will see a concept from domain model design: Aggregate. We solve this problem with aggregation.

  Microservices and Databases

  A very obvious feature of the microservices architecture is that the data owned by a service can only be accessed through the service's API.

  In an e-commerce website, for example, OrderService owns a database with a table ORDERS; CustomerService also has its own database containing the table CUSTOMERS.

  Through such encapsulation, microservices are decoupled.

  During development, developers can independently modify the database shema of their own services without coordinating with the development of other services.

  In production, services are isolated from one another. For example, a service will never block waiting because another service holds a lock on the database.

  Unfortunately, this database split makes it difficult to manage data consistency and query across tables across different services.

  Problem 2 - Distributed transaction implementation across services

  一个传统的单体应用可以通过ACID事务来强制业务规则从而实现一致性。

  想象一下,比如,电商里的用户都有信用额度,就是在创建订单之前必须先看信用如何。

  应用程序必须确保潜在的多个并发尝试去创建订单不超过客户的信用限额。

  如果Orders和Customers都在同一个库中,那么就可以使用ACID事务来搞定:

  BEGIN TRANSACTION…SELECT ORDER_TOTAL FROM ORDERS WHERE CUSTOMER_ID = ?…SELECT CREDIT_LIMITFROM CUSTOMERS WHERE CUSTOMER_ID = ?…INSERT INTO ORDERS ……COMMIT TRANSACTION

  不幸的是,在微服务架构中我们无法通过这种方式管理数据的一致性。

  ORDERS和CUSTOMERS表被不同的服务所拥有,只能通过各自的服务API访问。他们甚至可能在不同的数据库。

  一种比较常见的做法就是使用分布式事务来搞定,比如2PC等。但是这种做法对于现代应用来说也许不是一种可行的方案。CAP定理要求你必须在可用性和一致性之间选择,可用性通常是较好的选择。

  而且,许多现代技术,例如大多数NoSQL数据库,甚至不支持ACID事务,更不用说2PC。

  所以管理数据的一致性需要使用其他的方式。

  稍后你将会看到我们使用事件驱动架构中的一种技术叫事件源(event sourcing)来解决分布式事务。

  问题3 -查询

  管理数据一致性不是唯一的挑战。还有一个问题就是查询问题。

  在传统的单体应用中,我们通常使用join来实现跨表查询。

  比如,我们可以通过下面的sql轻松的查询出最近客户所订的大额订单:

  SELECT *

  FROM CUSTOMER c, ORDER o

  WHERE

  c.id = o.ID

  AND o.ORDER_TOTAL > 100000

  AND o.STATE = ‘SHIPPED’

  AND c.CREATION_DATE > ?

  但我们无法在微服务架构中实现这样的查询。

  就像前面提到的那样,ORDERS与CUSTOMERS表分属不同的服务,只能通过服务API来访问。

  而且他们可能使用了不同的数据库。

  而且,即使你使用事件源(Event Sourcing )处理查询问题可能更麻烦。

  稍后,你将会学习到一种解决方案就是通过一种叫CQRS(Command Query Responsibility Segregation)做法来解决分布式查询问题。

  但首先,让我们看看领域驱动设计(DDD)这个工具,在我们的微服务架构下基于领域模型开发业务逻辑是必要的。

  DDD聚合是微服务的构建块

  像你看到的那样,为了使用微服务架构成功的开发业务应用,我们必须去解决上面所说的那些问题。

  这几个问题的解决办法你可以去Eric Evans的书Domain-Driven Design中找得到。

  这本书,是2003年出版的,主要介绍了设计复杂软件的一些方法。这些方法对开发微服务也同样有用。

  尤其是领域驱动设计可以让你创建一个模块化的领域模型,这个领域模型可以被多个微服务所使用。

  什么是聚合?

  在领域驱动设计中,Evans为领域模型定义了几个构建块。

  许多已经成为日常开发人员语言的一部分,包括entity,就是指一个具有唯一标识的持久化对象。value object,也就是VO,你经常听说的,是用来存放数据的,可以与数据库表对应,也可以不对应,有点类似用来传输数据的DTO。service,就是指包含业务逻辑的服务。但不应归类到entity或者value object。

  repository,表示一堆entity 的集合就是一个repository。

  构建块(building block),聚合(aggregate)常常被开发人员忽略,除了那些DDD爱好者,或者叫“狂热分子”。

  然而,聚合(aggregate)被证明是开发微服务的关键,非常重要。

  一个聚合(aggregate)就是一组domain的集合,可以被当作一个单元来处理。这里说的一个单元就是可以当做原子来处理。

  它包含了一个root entity以及可能还有一到多个关联的entity以及value object。

  比如,针对一个在线商店的domain model就会有几个聚合,比如Order和Customer。

  Order聚合又由一个root entity Order和一个以上的OrderLineItem value object组成,而且OrderLineItem还有可能关联有其他vo,比如快递地址(Address)以及支付账户信息PaymentInformation。

  Customer聚合又由一个root entity Customer和其他的vo比如DeliveryInfo 和PaymentInformation组成。

  

  )

  使用聚合将领域模型(domain model)分散和参与到每个聚合中,这也使得领域模型更容易理解了。这也同时厘清了操作的scope,比如查询操作和删除操作等。

  一个聚合通常作为一个整体被从数据库中load出来。删除一个聚合,也就是删除了里边所有的object。

  然而,聚合的好处远远超出了模块化一个领域模型。 这是因为聚合必须遵守一定的规则。

  聚合之间的引用必须使用主键

  第一个规则就是聚合通过id(例如主键)来引用而不是通过对象引用 。

  比如,Order通过customerId来引用Customer,而不是引用Customer的对象。

  类似的,OrderLineItem通过productId来引用Product。

  

  )

  这种做法与传统的object modeling非常的不同。虽然后者认为通过外键引用在领域模型中这样做看起来怪怪的。

  通过使用ID而不是object引用,意味着聚合是松耦合。你可以轻松地把不同的聚合放在不同的service。

  事实上,一个微服务的业务逻辑是由一个领域模型组成。这个领域模型是几个聚合的一个组合。比如,OrderService包含了Customer聚合。

  一个事务只创建或更新一个聚合

  第二个规则就是聚合必须遵循一个事务只能对一个聚合进行创建或更新。

  当我第一次看这些规则的时候,当时并没有什么感觉。因为那时候,我还在开发传统的单体应用,那种基于RDBMS的应用。所以事务可以更新任何的数据。今天,这些约束依然适用于微服务架构。它确保一个事务只被包含在一个微服务中。此约束还符合大多数NoSQL数据库的有限事务模型。

  当开发一个领域模型,一个很重要的事情就是你必须确定每个聚合得搞多大。

  一方面,聚合理想情况下应该是小的。它通过分离关注点来改善模块化。

  这是更有效的,因为聚合通常被全部加载。

  此外,由于对每个聚合的更新是顺序发生的,因此使用细粒度聚合将增加应用程序可以处理的并发请求数,从而提高可扩展性。

  它还将改善用户体验,因为它降低了两个用户尝试更新同一聚合的可能性。

  另一方面,因为聚合是事务的范围,您可能需要定义一个较大的聚合,以使特定的更新原子化。

  例如,之前我描述了在在线商店领域模型中,Order和Customer是独立的聚合。

  另一种设计可以是把Orders作为Customer聚合的一部分。

  一个较大的Customer聚合的好处就是应用可以强制对于信用额度进行原子验证。这种方法的缺点是它将订单和客户管理功能组合到同一服务中。这也降低了可扩展性,因为更新同一客户的不同订单的事务将被顺序化。

  类似的,两个用户去尝试编辑同一个客户下的不同订单有可能会冲突。而且,随着订单数量的增加,加载一个Customer聚合的成本也会变得更昂贵。

  由于这些问题,尽可能的把聚合细粒度是最好的。

  即使一个事务只能创建和更新一个单独的聚合,微服务应用中也依然必须去管理聚合之间的一致性。

  在Order服务中必须验证一个新建的Order聚合将不超过Customer聚合的信用额度。

  这里有两种不同的解决一致性的方法。

  一个做法就是在单个事务中欺骗的创建和/或更新多个聚合。这种做法的前提是,所有的聚合都被一个服务所拥有并且这些聚合都被持久保存在同一个RDBMS中才有可能。

  另一个做法就是使用最终一致的事件驱动(event-driven)方法来维护聚合之间的一致性。

  使用事件驱动来维护数据一致性

  在现代应用中,对事务有各种约束,这使得难以在服务之间维持数据一致性。

  每个服务都有自己的私有的数据,这时候2PC的方案就变得不可行了。

  更重要的是,很多的应用使用的是NoSQL数据库,这些数据库根本就不支持本地ACID事务,更不用说分布式事务了。

  因此,现代应用程序必须使用事件驱动的,最终一致的事务模型。

  什么是事件(Event)

  根据 Merriam-Webster(一个单词网站),事件的意思就是: something that happens:

  

  )

  在本文中,我们将领域事件定义为聚合发生的事件。一个事件(event)通常表示一个状态的改变。现在还是拿电商系统举例,一个Order聚合。其状态更改事件包括订单已创建(Order Created),订单已取消(Order Cancelled),订单已下达(Order Shipped)。事件可以表示违反业务规则的动作,如客户(Customer)的信用额度。

  使用Event-Driven架构

  服务们使用事件来管理聚合之间的一致性,像下面这样的一个场景:一个聚合发布事件,比如,这个聚合的状态改变或者一次违反业务规则的尝试等等。

  其它聚合订阅这个事件,然后负责更新他们自己的状态。

  在线商店制创建一个订单(order)的时候验证客户(customer)信用额度使用下面一系列步骤:

  1. 一个订单(Order)聚合创建,并且状态为NEW,发布一个OrderCreated 事件。

      2.客户(Customer)消费这个OrderCreated事件,然后保存为这个订单保存信用值然后发布一个CreditReserved事件。

      3.订单(Order)聚合消费CreditReserved事件,然后修改自己的状态为APPROVED。

      如果信用检查由于资金不足而失败,则客户(Customer)聚合发布CreditLimitExceeded事件。

  这个事件不对应于一个状态的改变,而是表示一次违反业务规则的失败尝试。 订单(Order)聚合消费这个事件后,并将自己的状态更改为CANCELLED。

  微服务架构可以比作事件驱动聚合的Web

  在这个架构下,每个服务的业务逻辑都是由一个或多个聚合组成。

  一个事务只能包含一个服务,并且是更新或创建一个单独的聚合。也就是聚合内事务。

  服务们通过使用事件管理聚合之间的一致性。

  

  )

  这种做法一个非常明显的好处就是一个个聚合变成了松散而解耦的构建块。

  他们可以被作为单体应用来部署或者作为一组服务来部署。

  这种情况下,在一个project开始的时候,你可以使用单体架构。

  之后,随着应用的体积和开发团队的规模的扩大,你就可以很容易的切换到微服务架构上来。

  总结

  微服务架构从功能上把一整个应用拆分成了一个个服务,每个服务又都对应一个业务能力。当我们开发基于微服务架构的业务应用的时候,一个关键的挑战就是事务、领域模型以及查询,这三个主要的麻烦都是拆分之后所带来的问题。你可以通过使用DDD聚合的概念来拆分领域模型。每个服务的业务逻辑是一个领域模型,然后这个领域模型是由一个或多个DDD聚合组成。

  在每个服务中,一个事务只能创建或更新一个单独的聚合。由于2PC对于现代应用来说并不是一个可行的解决方案,所以我们需要使用事件机制来去实现聚合之间的一致性(以及服务之间)。在下一集,我们会描述使用event sourcing来实现一个事件驱动的架构。我们也会向你展示在微服务架构下通过使用CQRS来实现查询。

  关于作者:Chris Richardson是一位开发人员和建筑师。 他是Java Champion和POJO in Action的作者,他描述了如何使用Spring和Hibernate等框架构建企业Java应用程序。 Chris也是原始CloudFoundry.com的创始人。 他与组织协商,改进他们如何开发和部署应用程序,并在他的第三个创业公司工作。 你可以在Twitter @crichardson和Eventuate上找到Chris。

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326387002&siteId=291194637