Mass order system microservice development

Order system is a very important part of e-commerce platform, and it is also a system with huge traffic and high concurrent access. The services related to order involve inventory, payment, logistics and so on. When designing the order system, we choose to use NoSQL database MongoDB, which supports massive Data, with the reactive Spring Data MongoDB, to achieve high concurrency design.

The sample project code for this chapter can be downloaded from the source code of this book, checked out in IDEA, or directly downloaded from the page. After checking out, obtain branch version V2.1. There are several modules in this branch:

  • Order-object: Order common object design.
  • Order-restapi: Order microservice interface application design.
  • Order-web: Order background management application design.

Use MongoDB to support massive data

MongoDB is a distributed database, for development and debugging, we only need a stand-alone version.

Use the Mongo plugin

If you are using the IDEA development tool, you can also install a Mongo client plug-in to facilitate querying the database. Start IDEA and search for Mongo to install the IDEA. After the installation is complete, as shown in Figure 8-1.

After installing the plug-in, you can connect to MongoDB through Other Settings in your Settings and use the client to query the data. Figure 8-2 shows an example configuration of a local database connection.

Configure the MongoDB data source

We design MongoDB in the order-restAPI module, and first introduce related dependency references in the project object model POM.xml, as shown in the code below:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
Copy the code

Referenced here is the reactive Spring Data MongoDB component, which supports transaction-free, highly concurrent, non-blocking asynchronous request invocations.

In the module configuration file applicaption.yml, set the data source configuration connecting to the MongoDB server, as shown below:

#datasourcespring: data: mongodb: host: localhostport: 27017 # Mongo query time Jackson: timezone: GMT+8Copy the code

Here is a simple configuration of a local connection for a development environment, and if it is a production environment, you can set a user name and password, and specify the database name to use.

Here is a simple configuration of a local connection for a development environment, and if it is a production environment, you can set a user name and password, and specify the database name to use.

Because MongoDB uses Greenwich Mean Time (GMT), we configured “GMT+8” during data query in order to display the correct time of east zone 8.

Order document Modeling

The order data is mainly composed of the order and its detailed data. Since the order will undergo a series of state changes from the generation to the end of the transaction, and these states can generally be fixed, so an enumeration class can be used to achieve this.

Order and detailed data

The Order document is modeled by the Order class as follows:

@document@data @noargsConstructorPublic class Order {// Order ID @id private String ID; @indexed (name = "index orderNo") private String orderNo; // userid private Long userid; Private Long merchantid; Private Double amount; // Order status (0: unpaid, 1: paid,2: shipped,3: received,4: evaluated,-1: cancelled,-2: refunded) private Integer status; @datetimeformat (pattern= "YYYY-MM-DD HH: MM: ss")private Date created; // operator private string operator; @datetimeformat (pattern = "YYYY-MM-DD HH: MM :ss")private Date modify; Private List<OrderDetail> orderDetails = new ArrayList<>(); }Copy the code

In the code above, the properties of each field are commented. The @data annotation automatically generates getter/setter methods for each field. In addition, the annotation @ID is automatically generated by the database and is the unique index of the document; The annotation @Indexed creates an index for order numbers, which improves the performance of querying by order number.

The order details are defined in the class OrderDetail as follows:

@data public class OrderDetail {private Long goodsid; // Private String goodsName; // private String photo; Private Integer nums; // private Double price; // private Double money; // Timestamp @dateTimeFormat (pattern = "YYYY-MM-DD HH: MM :ss") private Date created; }Copy the code

In order details design, the method of redundant design for commodity name and picture data can reduce the invocation of commodity interface in inventory management.

Although the order details are a separate class, they are not a separate document. The order details will form a document with the order. This is different from the design of a relational database. In MySQL, the order details use a separate table structure and then use relational relationships to retrieve the data during queries, which can be very performance costly.

Order status enumeration

The order status, when saved in the order document, is an integer field that corresponds to one of the status information of the order. Generally speaking, this kind of state is relatively fixed, so we use an enumeration to define StatusEnum to achieve, so that in order query design, can be converted to each order state, at the same time in order editing can also list all states for selection. The code looks like this:

New Jersey public enum StatusEnum {New Jersey (2004), New Jersey (New Jersey), New Jersey (New Jersey), New Jersey (New Jersey), New Jersey (New Jersey) Checked (integer.valueof (2)," SHIPPED "), RECEIVED(Integer.valueof(3)," RECEIVED "), Checked (Integer.valueof(4)," EVALUATED "), REVOKED(INTEger.valueof (-1)," REVOKED "), REFUNDED(integer.valueof (-2)," REFUNDED "); private Integer code; private String name; StatusEnum(Integer code, String name) { this.code = code; this.name = name; public static boolean contains (Integer code) throws NullPointerException { if(null -= code){ throw new NullPointerException ("constant code is null"); ] else { StatusEnum[] varl = values(); int var2=var1. length; for(int var3 =0; var3 < var2; ++var3) { StatusEnum eum = varl[var3]; if(code.equals(eum.getCode())) { return true; return false; public static StatusEnum valueof(Integer code) throws NullPointerException,EnumConstantNotPresentException { if(null == code) { throw new NullPointerException ("constant code is null"); ] else { StatusEnum[] var1 =values(); int var2 = var1 . length; for(int var3 =0; var3 < var2; ++var3) { StatusEnum statusEnum= var1 [var3]; if(code.equals(statusEnum.getCode())) { return statusEnum; throw new EnumConstantNotPresentException (StatusEnum.class, code.toString()); ) public Integer getCode( { return this.code; } public string getName(){ return this.name; }}Copy the code

Reactive MongoDB programming design

Reactive programming is a new feature in Spring Boot 2.0 and above. It is a non-blocking asynchronous invocation design that can accommodate high concurrency requests. There are two basic concepts in reactive programming :Flux and Mono. Flux represents an asynchronous sequence of 0 to N elements that can contain three different types of message notifications: a normal message containing an element, a message at the end of the sequence, and a message with an error in the sequence. The corresponding subscriber methods onNext(), onComplete(), and onError() are called when message notifications are generated. Mono represents an asynchronous sequence of 0 or 1 elements in which the message notifications are of the same type as Flux.

Repository interface design based on Spring Data

Spring Data MongoDB, like Spring Data, has a unified specification design. We used this specification in the Spring DataJPA earlier, so the following code will feel familiar.

The repository interface for the order is OrderRepository, which is implemented as follows:

@Repository
ePrimary
public interface OrderRepository extends ReactiveMongoRepository<Order, String>{
Mono<0rder> findByOrderNo (String orderNo);
}
Copy the code

Dynamic paging query design

In your repository interface design, you can use the @Query annotation to flexibly define complex queries. For paging queries for orders, we used the following dynamic query design:

@Query ("I 'userid':? #(([0] == null)? {$exists:true}:[0]},"+ " 'merchantid':? #{([1] == null)? {$exists:true}:[1]},"+" 'status' :? #{([2] == null)? {$exists:true} :[2]1,"+ " 'created':? #{([3] == null) and ([4] == null)? {Sexists:true}:( $gte: [3],$lte: [4]}}}") Flux<Order> findAll (Long userid, Long merchantid, Integer status, Date start, Date end, Sort sort) ;Copy the code

Here we provide several query criteria, which are userID, merchantid, order Status, and order creation date. If the value of these query criteria is null, they are ignored; otherwise, the query is qualified by the value provided. Where, for the conditional query of the creation date of the order, the condition is greater than or equal to (Sgte) start date and less than or equal to ($Ite) end date. Finally, you can sort the query results.

For the paging query interface declaration, we used the following design in the service class OrderService:

@service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
public Flux<0rder> findAll (0rderQo orderQo){
try{
Sort sort = Sort.by (new Sort.Order(Sort.Direction.DESC, "created"));
return orderRepository. findAll(orderQo.getUserid(),
orderQo-getMerchantid(),
orderQo.getStatus(),orderQo.getStart(),orderQo.getEnd(),
sort)
.skip(orderQo.getPage() * orderQo.getSize()).limitRequest (orderQo.getSize());
}catch(Exception e){
e.printstackTrace();return null;
public Mono<Long> getCount(){
return orderRepository. count();
}
}
Copy the code

The order creation date is sorted in reverse order first, then the query parameters are transferred using the query object OrderQo, and the query results are paged out. Note that the output here is an asynchronous sequence Flux, which contains the list data for the order. If the data output is for a single object, you can use the asynchronous sequence Mono, as in the code above for the output of the order total query.

Mongo unit tests

For the pure database aspect of the previous design, we can verify this with a unit test. A test that generates order data would look like this:

@ RunWith (SpringRunner class) @ ContextConfiguration (classes = (0 rderrestapiapplication. Class)) @ SpringBootTest @ Sl4j public  class OrderTest { @Autowired private orderService orderService; @Test public void insertData(){ OrderDetail orderDetail1 =new OrderDetail(); OrderDetail1. SetGoodsname (" test items 1 "); orderDetail1.setGoodsid(1L); OrderDetaill. SetPrice (12.20 D); orderDetail1.setNums (1); OrderDetail1. SetMoney (12.20 D); orderDetail1 .setPhoto( ".. /images/demo1 .png") ; OrderDetail orderDetail2 = new OrderDetail(); OrderDetail2. SetGoodsname (" test items 2 "); orderDetail2.setGoodsid(2L); OrderDetail2. SetPrice (20.00 D); orderDetail2.setNums (2); OrderDetail2. SetMoney (40.00 D); orderDetai12.setPhoto (".. /images/demo2.png"); Order order = new Order(); order.setorderNo ( "123456"); order.setUserid(1213L); order. setMerchantid(2222L); Order. SetAmount (52.20 D); order.setStatus(1); order.setCreated (new Date()); List<0rderDetail> orderDetails = new ArrayEist<>(); Named orderDetails. Add (orderDetail1); orderDetails.add(orderDetail2); order.setOrderDetails(orderDetails); Mono<0rder> response = orderService.save (order); Assert.notNull(response, "save erro"); Log. The info (" return results: {} ", new Gson (). The toJson (response) block ())); }}Copy the code

In this test case design, an order is generated and two records are generated for the detailed data of the order. If you open the MongoDB debug log, you can see the following output from the console:

Inserting Document containing fields:[orderNo,userid,merchantid,amount,status,created,orderDetails, class]in collection: order
Copy the code

In addition, in order to see the test results more clearly, we also printed the generated order information in the log output with “Return result :0}”.

In this case, MongoDB clients can also be used to query the test results.

Because the test is performing the reactive data operation in a thread, for asynchronous sequences, a blocking process like block() must be performed at the end of the call to the reactive, otherwise the desired result cannot be achieved.

In the following test case design of increase, deletion, change and check, block processing design was finally carried out. For example, to test paging queries, we used the following design:

@Test public void findAl1() throws Exception{ OrderQo orderQo = new OrderQo(); List<0rder> list = orderService.findAll(orderQo) . collectList().block(); Assert.notEmpty(list, "list is empty"); The log. The info (total: {"; List :{}",list.size(),new Gson().tojson (list)); }Copy the code

After executing this test case, you can see the MongoDB log output in the console log as follows:

find using query:{ "userid" :{ "Sexists" :true }, "merchantid":{ "$exists":true }, "status":{"Sexists" : true ], "created":( "$exists" : true }I fields:
Document{{} for class: class com.demo.order.restapi.domain.0rder in collection:order
Copy the code

Because no numeric values are provided for the query parameters, it is shown that this is a non-conditional query that looks up all records of the order by paging results.

When we specify data for these query parameters, we see query log output like the following:

find using query: "userid" : 1213, "merchantid" :2222, "status" :1, "created":["$gte":{"$date" :1564538018885 }, "$lte":( "$date" : 1567130018886]HIfields: Document{{ for class: class com.demo,order.restapi.domain.0rder incollection: order
Copy the code

Three things to watch ❤️

If you find this article helpful, I’d like to invite you to do three small favors for me:

  1. Like, forward, have your “like and comment”, is the motivation of my creation.

  2. Follow the public account “Java rotten pigskin” and share original knowledge from time to time.

  3. Also look forward to the follow-up article ing🚀

  4. [666] Scan the code to obtain the learning materials package

Article is not original, and all of us feel while reading the article good remember thumb up forward + attention oh ~ this article source: www.toutiao.com/i6901654789…