関連記事
はじめに
今回は、Vaadinを使ってSalesのUIを実装し、Productを呼び出して商品リストを表示した。
商品を選択すると、保険期間が連動します。簡単のため、保険契約者と被保険者は同じとする。 アプリケーションのアーキテクチャは貧血モデルを使用している。
専門語
JP | EN |
---|---|
保険期間 | Term |
保険料 | Premium |
保険金 | Amount |
画面の設計
Sales中にUIの実装
MainView
@Route("/main") // URLを設定します
class MainView extends VerticalLayout {
// 依存サービスの宣言
private SalesService salesService;
// sales => gateway => product
private ProductService productService;
private UnderwritingService underwritingService;
MainView(SalesService salesService, ProductService productService, UnderwritingService underwritingService) {
this.salesService = salesService;
this.productService = productService;
this.underwritingService = underwritingService;
add(new H1("Mini Life Insurance System"));
add(new Hr());
add(new H3("Customer Info"));
HorizontalLayout customerInfo = new HorizontalLayout();
customerInfo.add(new TextField("Name"));
customerInfo.add(new TextField("Age"));
customerInfo.add(new TextField("Gender"));
add(customerInfo);
add(new Hr());
HorizontalLayout plan = new HorizontalLayout();
Select<ProductOption> product = new Select();
product.setLabel("Product");
// Productサーベスを呼び出す、商品リストを取得する。
product.setItems(productService.queryAllProduct().getBody());
// プルダウンの設定する
product.setItemLabelGenerator(ProductOption::getProductName);
Select<TermOption> term = new Select();
// 商品を選択し、保険期間のプルダウンを設定する。
product.addValueChangeListener(event -> {
term.setLabel("Term");
term.setItems(productService.querySupportedTerm(event.getValue().getProductId()).getBody());
term.setItemLabelGenerator(TermOption::getDescription);
});
TextField amount = new TextField("Amount");
// 保険料の計算について、後で説明します
TextField premium = new TextField("Premium");
premium.addFocusListener(o -> {
BigDecimal prem = salesService.calculatePremium(
product.getValue().getProductId(), new BigDecimal(amount.getValue()));
o.getSource().setValue(prem.toString());
});
plan.add(product, term, amount, premium);
add(plan);
}
}
Sales中にFeignClientを実装
ProductService
@FeignClient(name = "product", url = "${gateway.domain}/product")
public interface ProductService {
// 商品リストを取得する
@GetMapping("/queryAllProduct")
ResponseEntity<List<ProductOption>> queryAllProduct();
// 保険期間の定義を取得する
@GetMapping("/{productCode}/querySupportedTerm")
ResponseEntity<List<TermOption>> querySupportedTerm(@PathVariable("productCode") String productCode);
}
Product実装
商品によって許可される保険期間が定義される。保険期間は商品によって異なる場合があります。そこで、Configクラスが設計される。これは商品の凝集性の高さの反映である。
Entity
ここでのEntityはJPAのEntityであり、DDDのEntityとは異なる。今後、段階的にリファクタリングし、DDDの概念を導入していきたいと思います。
Config
@Entity
public class Config implements Serializable {
// コード全体はgitにある
@Enumerated(EnumType.STRING)
private List<Term> supportedTerm;
@Enumerated(EnumType.STRING)
private List<PaymentType> paymentTypes;
public Config() {
}
public Config(int minAge, int maxAge,BigDecimal minAmount,
List<Term> supportedTerm, List<PaymentType> paymentTypes) {
this.minAmount = minAmount;
this.minAge = minAge;
this.maxAge = maxAge;
this.supportedTerm = supportedTerm;
this.paymentTypes = paymentTypes;
}
public List<Term> getSupportedTerm() {
return supportedTerm;
}
}
商品にはConfig属性がある
Product
@Entity
public class Product {
// コード全体はgitにある
@OneToOne(cascade = CascadeType.ALL)
private Config config;
public Config getConfig() {
return config;
}
public void setConfig(Config config) {
this.config = config;
}
}
Product中にサーベスの実装
ProductService
@Service
public class ProductService {
private ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
// DBから全ての商品を検索する
public List<ProductDTO> queryAllProduct() {
List<ProductDTO> products = new ArrayList<>();
productRepository.findAll().forEach(item -> {
products.add(new ProductDTO(item.getId(), item.getName()));
});
return products;
}
// DBから商品を検索し、Configを取得する
public List<TermDTO> querySupportedTerm(String productCode) {
return productRepository.findById(productCode).get()
.getConfig().getSupportedTerm().stream().map(
item -> new TermDTO(item.getTerm(), item.getDescription())
).collect(Collectors.toList());
}
}
Product中にRestAPIの実装
ProductController
@RestController
public class ProductController {
@Autowired
private ProductService productService;
// 商品リストを取得する
@GetMapping("/queryAllProduct")
public ResponseEntity queryAllProduct() {
return ResponseEntity.ok(productService.queryAllProduct());
}
// 保険期間の定義を取得する
@GetMapping("/{productCode}/querySupportedTerm")
ResponseEntity<List<TermDTO>> querySupportedTerm(@PathVariable String productCode) {
return ResponseEntity.ok(productService.querySupportedTerm(productCode));
}
}
商品の初期化
サービス開始時に2つの商品が初期化される。
ProductApplication
@SpringBootApplication
@EnableDiscoveryClient
public class ProductApplication {
@Autowired
private ProductRepository repository;
public static void main(String[] args) {
SpringApplication.run(ProductApplication.class, args);
}
@PostConstruct
public void init() {
repository.deleteAll();
Config config = new Config(18,
60,
new BigDecimal(300000),
Arrays.asList(Term.YEAR_1, Term.YEAR_3, Term.YEAR_10, Term.YEAR_20),
Arrays.asList(PaymentType.TYPE_MONTH, PaymentType.TYPE_YEAR));
Product product = new Product("P001", "安心医療", config);
repository.save(product);
config = new Config(18,
60,
new BigDecimal(1000000),
Arrays.asList(Term.YEAR_10, Term.YEAR_15, Term.YEAR_20),
Arrays.asList(PaymentType.TYPE_MONTH, PaymentType.TYPE_YEAR));
product = new Product("P002", "安心年金", config);
repository.save(product);
}
}
確認する
Eureka、Sales、Gateway、Product4つのアプリケーションを一緒に起動できます。
% curl http://127.0.0.1:8002/queryAllProduct
[{"productId":"P001","productName":"安心医療"},{"productId":"P002","productName":"安心年金"}]
% curl http://127.0.0.1:8002/P001/querySupportedTerm
[{"term":"1","description":"1年"},{"term":"3","description":"3年"},{"term":"10","description":"10年"},{"term":"20","description":"20年"}]
% curl http://127.0.0.1:8002/P002/querySupportedTerm
[{"term":"10","description":"10年"},{"term":"15","description":"15年"},{"term":"20","description":"20年"}]
Gateway経由で
% curl http://127.0.0.1:8000/product/queryAllProduct
[{"productId":"P001","productName":"安心医療"},{"productId":"P002","productName":"安心年金"}]
% curl http://127.0.0.1:8000/product/P002/querySupportedTerm
[{"term":"10","description":"10年"},{"term":"15","description":"15年"},{"term":"20","description":"20年"}]
% curl http://127.0.0.1:8000/product/P001/querySupportedTerm
[{"term":"1","description":"1年"},{"term":"3","description":"3年"},{"term":"10","description":"10年"},{"term":"20","description":"20年"}]
最後にお客様として試してみる。商品を選び、別の商品に切り替えると、保険期間が正確に取得される。
Source Code
終わり
- 最後まで読んでいただきありがとうございました。この小さなシステムは後ほど最適化する予定である。
- 何かご提案やご質問がありましたら、コメントをお待ちしています。
- 質問を残して、MainViewと依存サービスはどのようにデカップリングするのですか?