|
1 |
| -# Spring Boot Data Bean 并发聚合支持 |
| 1 | +# Spring Boot Data Bean Parallel Aggregation Support |
2 | 2 |
|
3 |
| -## 背景 |
| 3 | +## Background and purpose |
4 | 4 |
|
5 |
| -**后台接口在调用一些接口时, 为了写代码方便, 多数时候我们习惯串行调用, 即使这些接口调用并无依赖性.** |
| 5 | +When developing the background interface, in order to improve the development efficiency, we often write serial execution codes to call different interfaces, even if there is no dependency among these interfaces, which causes the last developed interface performance is low, and the data is not convenient to reuse. |
6 | 6 |
|
7 |
| -**此框架目的旨在保持开发的简便性, 并同时很方便地支持并发和数据复用** |
| 7 | +**This framework is designed to support parallel and data reuse while maintaining the development efficiency.** |
8 | 8 |
|
9 |
| -## 原理 |
| 9 | +Of course, in an extremely high concurrent scenario, the parallel call interface is not that helpful for performance improvement. However, it doesn't mean that this project is meaningless because most applications in the Internet don't have very high concurrent traffic. |
10 | 10 |
|
11 |
| -1. CountDownLatch + Future |
12 |
| -2. 目标参数依赖中, 需要查询接口获取的数据, 封装为一个任务交给线程池处理, 处理完成之后, 再统一目标方法 |
| 11 | +## Principle |
13 | 12 |
|
14 |
| -## 使用方法 |
| 13 | +1. CountDownLatch + Future + Recursion |
| 14 | +2. In order to get the target data, it will recursively analyze and obtain the dependencies required by the data. **There are two kinds of dependencies; one is other interface return value, the other is input parameter. The former needs to call other interfaces, this call will be packed as a task which is an asynchronous execution to get the result.** |
15 | 15 |
|
16 |
| -- DataBeanProvider 定义数据model提供者 |
17 |
| -- DataBeanConsumer 定义方法将要消费的model |
18 |
| -- InvokeParameter 指定用户手动传入的参数 |
19 |
| -- DataBeanAggregateQueryFacade 查询指定的model |
| 16 | +## Instruction |
20 | 17 |
|
21 |
| -## 示例 |
| 18 | +- `@DataProvider`: define the data provider |
| 19 | +- `@DataConsumer`: define the method parameter dependency type as return the value of other interfaces, the other interface is a @DataProvider |
| 20 | +- `@InvokeParameter`: define the method parameter dependency type as the user input value |
| 21 | +- Spring Bean `dataBeanAggregateQueryFacade`: query data facade API |
22 | 22 |
|
23 |
| -开发一个用户汇总数据接口, 包括用户的用户基础信息和博客列表 |
| 23 | +##Example |
24 | 24 |
|
25 |
| -**1. 定义提供基础数据的"原子"服务** |
| 25 | +Developing a user summary data interface that includes the user's basic information and blog list. |
26 | 26 |
|
27 |
| -使用DataBeanProvider定义要提供的数据, 使用InvokeParameter指定查询时要传递的参数 |
| 27 | +### 1. Define an "atomic" service to provide user data |
28 | 28 |
|
29 |
| -博客列表服务, 需要参数userId |
| 29 | +Use `@DataProvider` to define the interface a data provider. |
| 30 | + |
| 31 | +Use `@InvokeParameter` to specify the input parameters to pass. |
| 32 | + |
| 33 | +**Blog list service** |
| 34 | + |
| 35 | +require input parameter `userId`. |
30 | 36 |
|
31 | 37 | ```java
|
32 | 38 | @Service
|
33 |
| -public class PostServiceImpl implements PostService { |
34 |
| - @DataBeanProvider(id = "posts") |
35 |
| - @Override |
36 |
| - public List<Post> getPosts(@InvokeParameter("userId") Long userId) { |
37 |
| - try { |
38 |
| - Thread.sleep(1000L); |
39 |
| - } catch (InterruptedException e) { |
40 |
| - // |
41 |
| - } |
42 |
| - Post post = new Post(); |
43 |
| - post.setTitle("spring data aggregate example"); |
44 |
| - post.setContent("No active profile set, falling back to default profiles"); |
45 |
| - return Collections.singletonList(post); |
46 |
| - } |
| 39 | +Public class PostServiceImpl implements PostService { |
| 40 | + @DataProvider(id = "posts") |
| 41 | + @Override |
| 42 | + Public List<Post> getPosts(@InvokeParameter("userId") Long userId) { |
| 43 | + Try { |
| 44 | + Thread.sleep(1000L); |
| 45 | + } catch (InterruptedException e) { |
| 46 | + } |
| 47 | + Post post = new Post(); |
| 48 | + post.setTitle("spring data aggregate example"); |
| 49 | + post.setContent("No active profile set, falling back to default profiles"); |
| 50 | + Return Collections.singletonList(post); |
| 51 | + } |
47 | 52 | }
|
48 |
| - |
49 | 53 | ```
|
50 | 54 |
|
51 |
| -用户基础信息查询服务, 需要参数userId |
| 55 | +**User basic information query service** |
| 56 | + |
| 57 | +require input parameter `userId`. |
52 | 58 |
|
53 | 59 | ```java
|
54 | 60 | @Service
|
55 |
| -public class UserServiceImpl implements UserService { |
56 |
| - |
57 |
| - @DataBeanProvider(id = "user") |
58 |
| - @Override |
59 |
| - public User get(@InvokeParameter("userId") Long id) { |
60 |
| - /* */ |
61 |
| - try { |
62 |
| - Thread.sleep(100L); |
63 |
| - } catch (InterruptedException e) { |
64 |
| - // |
65 |
| - } |
66 |
| - /* mock a user*/ |
67 |
| - User user = new User(); |
68 |
| - user.setId(id); |
69 |
| - user .setEmail( "[email protected]"); |
70 |
| - user.setUsername("lvyahui8"); |
71 |
| - return user; |
72 |
| - } |
| 61 | +Public class UserServiceImpl implements UserService { |
| 62 | + |
| 63 | + @DataProvider(id = "user") |
| 64 | + @Override |
| 65 | + Public User get(@InvokeParameter("userId") Long id) { |
| 66 | + /* */ |
| 67 | + Try { |
| 68 | + Thread.sleep(100L); |
| 69 | + } catch (InterruptedException e) { |
| 70 | + } |
| 71 | + /* mock a user*/ |
| 72 | + User user = new User(); |
| 73 | + user.setId(id); |
| 74 | + user .setEmail( "[email protected]"); |
| 75 | + user.setUsername("lvyahui8"); |
| 76 | + Return user; |
| 77 | + } |
73 | 78 | }
|
74 | 79 | ```
|
75 | 80 |
|
76 |
| -**2. 定义并实现聚合层** |
| 81 | +### 2. Define and implement an aggregation layer |
77 | 82 |
|
78 |
| -组合DataBeanProvider\DataBeanConsumer\InvokeParameter实现汇聚功能 |
| 83 | +Combine `@DataProvider` ( `@DataConsumer` \ `@InvokeParameter` ) to achieve aggregation function |
79 | 84 |
|
80 | 85 | ```java
|
81 | 86 | @Component
|
82 |
| -public class UserAggregate { |
83 |
| - @DataBeanProvider(id="userWithPosts") |
84 |
| - public User userWithPosts( |
85 |
| - @DataBeanConsumer(id = "user") User user, |
86 |
| - @DataBeanConsumer(id = "posts") List<Post> posts) { |
87 |
| - user.setPosts(posts); |
88 |
| - return user; |
89 |
| - } |
| 87 | +Public class UserAggregate { |
| 88 | + @DataProvider(id="userWithPosts") |
| 89 | + Public User userWithPosts( |
| 90 | + @DataConsumer(id = "user") User user, |
| 91 | + @DataConsumer(id = "posts") List<Post> posts) { |
| 92 | + user.setPosts(posts); |
| 93 | + Return user; |
| 94 | + } |
90 | 95 | }
|
91 | 96 | ```
|
92 | 97 |
|
93 |
| -**3. 调用聚合层接口** |
| 98 | +### 3. Call the aggregation layer interface |
94 | 99 |
|
95 |
| -注解了DataBeanProvider方法的接口都不能直接调用, 而需要通过门面类DataBeanAggregateQueryFacade访问. |
| 100 | +Note that the interface of the `@DataProvider` method shouldn't to be called directly, but accessed through the facade class `DataBeanAggregateQueryFacade`. |
96 | 101 |
|
97 |
| -指定要查询的DataBeanProvider.id, 查询参数, 返回值类型即可 |
| 102 | +Specify queried data id, invoke parameters, and return type to invoke `facade.get` method |
98 | 103 |
|
99 | 104 | ```java
|
100 |
| -@SpringBootApplication |
101 |
| -public class ExampleApplication { |
102 |
| - public static void main(String[] args) throws Exception { |
103 |
| - ConfigurableApplicationContext context = SpringApplication.run(ExampleApplication.class); |
104 |
| - DataBeanAggregateQueryFacade queryFacade = context.getBean(DataBeanAggregateQueryFacade.class); |
105 |
| - User user = queryFacade.get("userWithPosts", Collections.singletonMap("userId",1L), User.class); |
106 |
| - Assert.notNull(user,"user not null"); |
107 |
| - Assert.notNull(user.getPosts(),"user posts not null"); |
108 |
| - System.out.println(user); |
109 |
| - } |
110 |
| -} |
| 105 | +DataBeanAggregateQueryFacade queryFacade = context.getBean(DataBeanAggregateQueryFacade.class); |
| 106 | +User user = queryFacade.get(/*data id*/ "userWithPosts", |
| 107 | + /*Invoke Parameters*/ |
| 108 | + Collections.singletonMap("userId",1L), |
| 109 | + User.class); |
| 110 | +Assert.notNull(user,"user not null"); |
| 111 | +Assert.notNull(user.getPosts(),"user posts not null"); |
111 | 112 | ```
|
112 | 113 |
|
113 |
| -**运行结果** |
| 114 | +**Invoke result** |
114 | 115 |
|
115 |
| -可以看到, user 和posts是由异步线程进行的查询, 而userWithPosts是主调线程 |
| 116 | +As you can see, `user` interface and `posts` interface are executed by the asynchronous thread while `userWithPosts` service is executed by the calling thread. |
116 | 117 |
|
117 |
| -其中 |
118 |
| - |
119 |
| -- 基础user信息查询时间 1000ms |
120 |
| -- 用户博客列表查询时间 1000ms |
121 |
| -- **总的查询时间 1005ms** |
| 118 | +- Basic user information query takes 1000ms |
| 119 | +- User blog list query takes 1000ms |
| 120 | +- **Total query takes 1005ms** |
122 | 121 |
|
123 | 122 | ```
|
124 |
| -2019-06-03 23:56:52.254 INFO 9088 --- [aggregateTask-2] f.s.a.s.DataBeanAgregateQueryServiceImpl : query id: posts, costTime: 1000ms, model: List, params: 1 |
125 |
| -2019-06-03 23:56:52.254 INFO 9088 --- [aggregateTask-1] f.s.a.s.DataBeanAgregateQueryServiceImpl : query id: user, costTime: 1000ms, model: User, params: 1 |
126 |
| -2019-06-03 23:56:52.255 INFO 9088 --- [ main] f.s.a.s.DataBeanAgregateQueryServiceImpl : query id: userWithPosts, costTime: 1005ms, model: User, params: User........... |
| 123 | +[aggregateTask-1] query id: user, costTime: 1000ms, resultType: User, invokeMethod: UserServiceImpl#get |
| 124 | +[aggregateTask-2] query id: posts, costTime: 1000ms, resultType: List, invokeMethod: PostServiceImpl#getPosts |
| 125 | +[ main ] query id: userWithPosts, costTime: 1010ms, resultType: User, invokeMethod: UserAggregate#userWithPosts |
| 126 | +[ main] user.name:lvyahui8,user.posts.size:1 |
127 | 127 | ```
|
128 | 128 |
|
129 |
| -## 作者 |
| 129 | +## Contributors |
| 130 | + |
| 131 | + |
130 | 132 |
|
131 |
| - |
| 133 | +- Iris G |
0 commit comments