1. Introduction
In business development, we often need to make different interfaces compatible, especially the middle office services. The middle office needs to uniformly package various types of services of each business line, and then provide external interfaces for use.
2. Case scenario simulation
With the continuous development of the business, when the basic system is gradually formed, the business operation needs to start to innovate and promote users, so as to ensure the growth rate of DUA and the final ROI conversion.
A marketing system is simulated here. For example, if you invite a user to open an account, or invite a user to place an order, the platform will give you a rebate. Invite more and get more. At the same time, with more and more new products, we began to set various marketing scenarios, such as giving rewards to the first order when placing an order every month.
At this time, such a system will receive all kinds of MQ messages or interfaces. If it is developed one by one, it will cost a lot of cost and is not conducive to later expansion. At this time, it is hoped that a system can be configured to access external MQ. These MQ, as mentioned above, may be registration account opening messages, commodity order messages, etc.
The idea of adapter can be applied here, and the adapter can adapt not only the interface, but also some attribute information.
Scene simulation engineering
org.itstack.demo.design ----mq ----create_account.java ----OrderMq.java ----POPOrderDelivered.java ----service ----OrderService.java ----POPOrderService.java
- Here, three different types of MQ messages are simulated, and there are some necessary fields in the message body, such as user ID, time and business ID, but the field properties of each MQ are different. For example, user ID also has different field names in different MQ: uid, userId, etc.
- At the same time, it also provides two different types of interfaces, one for querying the quantity of internal orders and the other for querying whether the third party has the first order.
Scenario description
1. Registered account opening MQ
public class create_account { private String number; // Account No private String address; // Place of account opening private Date accountDate; // Account opening time private String desc; // Account opening description // ... get/set }
2. Internal order MQ
public class OrderMq { private String uid; // User ID private String sku; // commodity private String orderId; // Order ID private Date createOrderTime; // Order time // ... get/set }
3. Third party order MQ
public class POPOrderDelivered { private String uId; // User ID private String orderId; // order number private Date orderTime; // Order time private Date sku; // commodity private Date skuName; // Trade name private BigDecimal decimal; // amount of money // ... get/set }
4. Interface for querying internal order quantity of users
public class OrderService { private Logger logger =LoggerFactory.getLogger(POPOrderService.class); public long queryUserOrderCount(String userId){ logger.info("Self operated, query whether the user's order is the first order:{}", userId); return 10L; } }
5. Query the first order interface of the user's third-party order
public class POPOrderService { private Logger logger = LoggerFactory.getLogger(POPOrderService.class); public boolean isFirstOrder(String uId) { logger.info("POP Merchant, query whether the user's order is the first order:{}", uId); return true; } }
The above is an embodiment of different MQ and different interfaces. Later, we will use such MQ messages and interfaces to adapt them accordingly.
3. A lump of code implementation
Most of the time, when receiving an MQ message, you create a class for consumption by converting its MQ message properties to its own methods.
Next, let's show the implementation simulation in the following way, but there is a big problem in this implementation. When there are more and more MQ messages, or even hundreds of them, how do you optimize as a middle office?
engineering structure
org.itstack.demo.design ----create_accountMqService.java ----OrderMqService.java ----POPOrderDeliveredService.java
At present, we need to receive three kinds of MQ messages, so there are three corresponding classes, which is almost the same as our usual code habits. If the amount of MQ is small, there is no problem with this writing method, but with the increase of the number, some design patterns need to be considered for optimization.
MQ receive message implementation
public class create_accountMqService { public void onMessage(String message) { create_account mq = JSON.parseObject(message,create_account.class); mq.getNumber(); mq.getAccountDate(); // ... Business logic } }
The other two groups of MQ messages are the same, so we won't show them one by one.
4. Adapter pattern refactoring code
The main problem to be solved by the adapter mode is that multiple differentiated types of interfaces do unified output. In this paper, we will also reflect a scenario of multiple MQ receiving and using MQ to uniformly process different types of messages, so as to reduce subsequent MQ receiving.
If you haven't contacted MQ messages before, it's recommended to learn about them first, but even if you don't, it won't affect your experience of ideas.
In this paper, the core part of MQ compatibility shown in this paper is to deal with adapting different types of fields. After receiving MQ and configuring different consumer classes, if we don't want to develop classes one by one, name can use the proxy class for processing.
engineering structure
org.itstack.demo.design ----impl ----InsideOrderService.java ----POPOrderAdapterService.java ----MQAdapter.java ----OrderAdapterService.java ----RebateInfo.java
Adapter mode structure
- There are two types of adaptation: interface adaptation and MQ attribute adaptation. The reason is not just analog interface adaptation, because interface adaptation is very common, so convert the idea of adaptation to the MQ message body.
- First, do MQ adaptation and receive all kinds of MQ messages. When the business develops rapidly, it is necessary to reward the first order of the single user. In such a scenario, the interface adaptation operation is added.
Code implementation (MQ message adaptation)
1. Unified MQ message body
public class RebateInfo { private String userId; // User ID private String bizId; // Business ID private Date bizTime; // Business time private String desc; // Business description // ... get/set }
- There will be a variety of type attributes in MQ messages. Although they all have unified values for users, if they are accessed in this way, it will be troublesome when there are too many MQ messages.
- Therefore, here we define a general MQ message body, and then process all incoming messages uniformly.
2. MQ message body adaptation class
public class MQAdapter { public static RebateInfo filter(String strJson, Map<String, String> link) throws NoSuchMethodException, InvocationTargetException,IllegalAccessException { return filter(JSON.parseObject(strJson, Map.class), link); } // Convert to a unified MQ object public static RebateInfo filter(Map obj, Map<String, String> link) throws NoSuchMethodException, InvocationTargetException,IllegalAccessException { RebateInfo rebateInfo = new RebateInfo(); for (String key : link.keySet()) { Object val = obj.get(link.get(key)); RebateInfo.class.getMethod("set" + key.substring(0,1).toUpperCase() + key.substring(1), String.class).invoke(rebateInfo,val.toString()); } return rebateInfo; } }
- The methods in this class are very important. They are mainly used to map various attributes of different types of MQ into the attributes we need and return. For example, there is a user ID in an attribute, and the uid is mapped to the userId we need for unified processing.
- In this process, the mapping management needs to be passed to map < string, string > link, that is, it accurately describes a property name in the current MQ and maps it to a property name we need.
- Because the MQ messages we receive are basically in JSON format, which can be converted into MAP structure. Finally, we use reflection call to assign values to our types.
3. Test adaptation class
Writing unit test classes
@Test public void test_MQAdapter() throws NoSuchMethodException,IllegalAccessException, InvocationTargetException { // 1 create_account create_account = new create_account(); create_account.setNumber("100001"); create_account.setAddress("Hangzhou, xx university"); create_account.setAccountDate(new Date()); create_account.setDesc("School account opening"); HashMap<String, String> link01 = new HashMap<String, String>(); link01.put("userId", "number"); link01.put("bizId", "number"); link01.put("bizTime", "accountDate"); link01.put("desc", "desc"); RebateInfo rebateInfo01 = MQAdapter.filter(create_account.toString(),link01); System.out.println("mq.create_account(front)" +create_account.toString()); System.out.println("mq.create_account(after)"+JSON.toJSONString(rebateInfo01)); // 2 OrderMq orderMq = new OrderMq(); orderMq.setUid("100001"); orderMq.setSku("10928092093111123"); orderMq.setOrderId("100000890193847111"); orderMq.setCreateOrderTime(new Date()); HashMap<String, String> link02 = new HashMap<String, String>(); link02.put("userId", "uid"); link02.put("bizId", "orderId"); link02.put("bizTime", "createOrderTime"); RebateInfo rebateInfo02 = MQAdapter.filter(orderMq.toString(),link02); System.out.println("mq.orderMq(front)" + orderMq.toString()); System.out.println("mq.orderMq(after)" +JSON.toJSONString(rebateInfo02)); }
- Here, we simulate the incoming of two different MQ messages and set the mapping relationship of fields.
- Of course, in the actual business scenario, the configuration mapping relationship can be handed over to the configuration file or database background configuration.
test result
mq.create_account(front){"accountDate":1591024816000,"address":"Hangzhou, xx university","desc":"School account opening","number":"100001"} mq.create_account(after){"bizId":"100001","bizTime":1591077840669,"desc":"School account opening","userId":"100001"} mq.orderMq(front) {"createOrderTime":1591024816000,"orderId":"100000890193847111","sku":"1092 8092093111123","uid":"100001"} mq.orderMq(after) {"bizId":"100000890193847111","bizTime":1591077840669,"userId":"100001"} Process finished with exit code 0
- You can see that the same field values are processed with unified field attributes before and after adaptation.
- In the actual business development, in addition to the use of reflection, you can also add a proxy class and give it the configuration of the mapping, so that you don't need to manually create classes for every mq.
Code implementation (interface adaptation)
In the simulation scenario, add a condition: only the first user will be rewarded.
This method needs to be restricted, and the attribute of the first order is not judged in MQ at this time. You can only query through the interface, and the obtained interface is as follows:
Interface | describe |
---|---|
queryUserOrderCount(String userId) | Issue parameter long to query the order quantity |
isFirstOrder(String uId) | The parameter boolean is used to judge whether the order is the first one |
- The judgment logic and use mode of the two interfaces are different. Different interface providers also have different input parameters. Therefore, the adapter mode is needed here.
1. Define uniform adapter interface
public interface OrderAdapterService { boolean isFirst(String uId); }
- Later implementation classes need to complete this interface and wrap the specific logic into the specified class to meet a single responsibility.
2. Two different interfaces are implemented respectively
Internal commodity interface
public class InsideOrderService implements OrderAdapterService { private OrderService orderService = new OrderService(); public boolean isFirst(String uId) { return orderService.queryUserOrderCount(uId) <= 1; } }
Third party commodity interface
public class POPOrderAdapterServiceImpl implements OrderAdapterService { private POPOrderService popOrderService = new POPOrderService(); public boolean isFirst(String uId) { return popOrderService.isFirstOrder(uId); } }
- Both interfaces implement their own judgment methods, especially for the interface that provides order quantity, you need to judge whether the order quantity is < = 1 to judge whether it is the first order.
3. Test adaptation class
Unit test class
@Test public void test_itfAdapter() { OrderAdapterService popOrderAdapterService = new POPOrderAdapterServiceImpl(); System.out.println("Adaptation(POP): " + popOrderAdapterService.isFirst("100001")); OrderAdapterService insideOrderService = new InsideOrderService(); System.out.println("Adaptation(self-support): " + insideOrderService.isFirst("100001")); }
test result
23:25:47.076 [main] INFO o.i.d.design.service.POPOrderService - POP Query order: 100001 Adaptation(POP): true 23:25:47.079 [main] INFO o.i.d.design.service.POPOrderService - Self operated, query order: 100001 Adaptation(self-support): false Process finished with exit code 0
- According to the test results, the interface has been uniformly packaged, and the external use does not need to care about the internal specific logic. And when calling, you only need to pass in unified parameters.
5. Summary
- Using the adapter mode can make the code clean, tidy, easy to maintain and easy to expand
- In particular, the values of different attributes in a variety of message bodies such as MQ can be adapted. In addition, the proxy class can use a simple configuration method to access the MQ messages provided by the other party without a lot of repeated development.