1
2

ミニ生命保険システムを作成 S04

Last updated at Posted at 2023-09-16

関連記事

はじめに

今回は、Vaadinを使ってSalesのUIを実装し、Productを呼び出して商品リストを表示した。
商品を選択すると、保険期間が連動します。簡単のため、保険契約者と被保険者は同じとする。 アプリケーションのアーキテクチャは貧血モデルを使用している。

専門語

JP EN
保険期間 Term
保険料 Premium
保険金 Amount

画面の設計

UI.png

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の概念を導入していきたいと思います。

ProductUML.png

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年"}]

最後にお客様として試してみる。商品を選び、別の商品に切り替えると、保険期間が正確に取得される。

UI2.png

Source Code

終わり

  • 最後まで読んでいただきありがとうございました。この小さなシステムは後ほど最適化する予定である。
  • 何かご提案やご質問がありましたら、コメントをお待ちしています。
  • 質問を残して、MainViewと依存サービスはどのようにデカップリングするのですか?
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2