版權聲明:本文為部落客chszs的原創文章,未經部落客允許不得轉載。 https://blog.csdn.net/chszs/article/details/56289457
Spring Framework 5.0的響應式微服務
作者:chszs,未經部落客允許不得轉載。經許可的轉載需注明作者和部落格首頁: http://blog.csdn.net/chszs
Spring團隊已經宣布從5.0版本開始支援響應式程式設計模型。新的Spring 5.0版本可能會在今年3月釋出。幸運的是,包含這些特性的裡程碑版本和快照版本(非穩定版)現在可以從Spring存儲庫獲得。另外還有一個新的Spring Web Reactive項目,支援響應式的@Controller注解和一個新的WebClient的用戶端響應式。下面,可以進一步看看Spring團隊提出的解決方案。
遵循這個文檔,見:
http://docs.spring.io/spring-framework/docs/5.0.0.BUILD-SNAPSHOT/spring-framework-reference/html/web-reactive.htmlSpring架構在内部使用Reactor實作了對響應式的支援。Reactor是一個Reactive Streams的實作,它進一步擴充了基本的Reactive Streams Publisher以及Flux和Mono composable API,對資料序列0…N和0…1提供了聲明式的操作。在伺服器端,Spring支援基于注釋和函數式程式設計模型。注釋模型使用了@Controller注解和其他同時也支援Spring MVC的注解。對于同步服務,響應式控制器與标準REST控制器是非常相似的,下面說明如何怎樣使用注釋模型和MongoDB的響應式子產品來開發一個簡單的響應式微服務。
在例子中,使用了Spring Boot 2.0.0快照版和Spring Web Reactive 0.1.0版。依賴配置pom.xml的主要片段和單個微服務的内容如下。在微服務中,使用Netty來代替了預設的Tomcat伺服器。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot.experimental</groupId>
<artifactId>spring-boot-dependencies-web-reactive</artifactId>
<version>0.1.0.BUILD-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot.experimental</groupId>
<artifactId>spring-boot-starter-web-reactive</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.projectreactor.ipc</groupId>
<artifactId>reactor-netty</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>pl.piomin.services</groupId>
<artifactId>common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor.addons</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
有兩個微服務:帳戶服務和客戶服務。每個微服務都有自己的MongoDB資料庫,且對外暴露簡單的響應式API,用于搜尋和儲存資料。另外,客戶服務與帳戶服務可以互相通信,以擷取所有的客戶帳戶,并通過客戶服務API方法傳回。下面是帳戶控制器代碼:
@RestController
public class AccountController {
@Autowired
private AccountRepository repository;
@GetMapping(value = "/account/customer/{customer}")
public Flux<Account> findByCustomer(@PathVariable("customer") Integer customerId) {
return repository.findByCustomerId(customerId)
.map(a -> new Account(a.getId(), a.getCustomerId(), a.getNumber(), a.getAmount()));
}
@GetMapping(value = "/account")
public Flux<Account> findAll() {
return repository.findAll().map(a -> new Account(a.getId(), a.getCustomerId(), a.getNumber(), a.getAmount()));
}
@GetMapping(value = "/account/{id}")
public Mono<Account> findById(@PathVariable("id") Integer id) {
return repository.findById(id)
.map(a -> new Account(a.getId(), a.getCustomerId(), a.getNumber(), a.getAmount()));
}
@PostMapping("/person")
public Mono<Account> create(@RequestBody Publisher<Account> accountStream) {
return repository
.save(Mono.from(accountStream)
.map(a -> new pl.piomin.services.account.model.Account(a.getNumber(), a.getCustomerId(),
a.getAmount())))
.map(a -> new Account(a.getId(), a.getCustomerId(), a.getNumber(), a.getAmount()));
}
}
在所有API方法中,還執行common子產品從帳戶實體(MongoDB @Document注解的)到帳戶DTO的映射。下面是帳戶存儲庫類。它使用ReactiveMongoTemplate與Mongo集合進行互動。
@Repository
public class AccountRepository {
@Autowired
private ReactiveMongoTemplate template;
public Mono<Account> findById(Integer id) {
return template.findById(id, Account.class);
}
public Flux<Account> findAll() {
return template.findAll(Account.class);
}
public Flux<Account> findByCustomerId(String customerId) {
return template.find(query(where("customerId").is(customerId)), Account.class);
}
public Mono<Account> save(Mono<Account> account) {
return template.insert(account);
}
}
在Spring Boot的main或@Configuration類中,應該為MongoDB聲明Spring Bean以及連接配接設定。
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
public @Bean MongoClient mongoClient() {
return MongoClients.create("mongodb://192.168.99.100");
}
public @Bean ReactiveMongoTemplate reactiveMongoTemplate() {
return new ReactiveMongoTemplate(mongoClient(), "account");
}
}
使用docker MongoDB容器來處理這個示例。
docker run -d --name mongo -p 27017:27017 mongo
在客戶服務中,從帳戶服務調用端點的/account/customer/{customer}。在主類中聲明為@Bean WebClient。
@Autowired
private WebClient webClient;
@GetMapping(value = "/customer/accounts/{pesel}")
public Mono<Customer> findByPeselWithAccounts(@PathVariable("pesel") String pesel) {
return repository.findByPesel(pesel).flatMap(customer -> webClient.get().uri("/account/customer/{customer}", customer.getId()).accept(MediaType.APPLICATION_JSON)
.exchange().flatMap(response -> response.bodyToFlux(Account.class))).collectList().map(l -> {return new Customer(pesel, l);});
}
可以使用Web浏覽器或REST用戶端來測試GET調用。而用POST,則沒有那麼簡單。下面有兩個簡單的測試用例,用于添加新客戶和獲得客戶的帳戶資訊。要測試getCustomerAccounts,需要先在端口2222上運作帳戶服務。
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CustomerTest {
private static final Logger logger = Logger.getLogger("CustomerTest");
private WebClient webClient;
@LocalServerPort
private int port;
@Before
public void setup() {
this.webClient = WebClient.create("http://localhost:" + this.port);
}
@Test
public void getCustomerAccounts() {
Customer customer = this.webClient.get().uri("/customer/accounts/234543647565")
.accept(MediaType.APPLICATION_JSON).exchange().then(response -> response.bodyToMono(Customer.class))
.block();
logger.info("Customer: " + customer);
}
@Test
public void addCustomer() {
Customer customer = new Customer(null, "Adam", "Kowalski", "123456787654");
customer = webClient.post().uri("/customer").accept(MediaType.APPLICATION_JSON)
.exchange(BodyInserters.fromObject(customer)).then(response -> response.bodyToMono(Customer.class))
.block();
logger.info("Customer: " + customer);
}
}
結論
Spring架構開始支援響應式程式設計非常不錯,但現在它還處于早期階段,也沒法與Spring Cloud等項目一起使用。希望在不久的将來,類似服務發現和負載平衡的功能也可用于與Spring響應式微服務相內建。Spring還有一個Spring Cloud Stream項目,支援響應式模型。以後再看吧!