関連記事
はじめに
Salesの中に、MainViewと依存サービスはどのようにデカップリングするのですか?
今回は、CommandとCallBackを使ってMainViewをリファクタリングします。
@Route("/main")
class MainView extends VerticalLayout {
// bad smell
private SalesService salesService;
// bad smell
private ProductService productService;
// bad smell
private UnderwritingService underwritingService;
// bad smell
private ApplicationRecordRepository repository;
MainView(SalesService salesService,
ProductService productService,
UnderwritingService underwritingService,
ApplicationRecordRepository repository) {
実装
まず、MainViewに関連するビジネスロジックをメソッドにカプセル化する。保険申込を例にとってみましょう。
public UnderWritingResult application(=>?) {
return underwritingService.application(=>?).getBody();
}
ビジネスパラメーターはコマンドオブジェクトに入れられる。
public UnderWritingResult onApplicationCommand(ApplicationCommand cmd) {
return underwritingService.application(cmd.getApplication()).getBody();
}
次にアノテーションを追加する。
CommandHandler
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface CommandHandler {
}
@CommandHandler
public UnderWritingResult onApplicationCommand(ApplicationCommand cmd) {
return underwritingService.application(cmd.getApplication()).getBody();
}
Springの自動注入を使いたかったので、親クラスBaseCommandHandlerを作った。完全なクラスは以下のようになる。
UnderwritingCommandHandler
package com.insurance.life.sales.service;
@Component
public class UnderwritingCommandHandler implements BaseCommandHandler {
private UnderwritingService underwritingService;
public UnderwritingCommandHandler(UnderwritingService underwritingService) {
this.underwritingService = underwritingService;
}
@CommandHandler
public UnderWritingResult onApplicationCommand(ApplicationCommand cmd) {
return underwritingService.application(cmd.getApplication()).getBody();
}
}
次に、これらのHandlerを起動時にコンテナに入れる。コンマッドの名前をキーにする。
CommandExecutor
private Map<String, CommandHandlerFunction> context = new HashMap<>();
// handlerList Autowired
public CommandExecutor(List<BaseCommandHandler> handlerList) {
handlerList.stream().forEach(handler -> {
Arrays.stream(handler.getClass().getMethods()).forEach(method -> {
if (method.isAnnotationPresent(CommandHandler.class)) {
// key
String commandName = method.getParameterTypes()[0].getName();
context.put(commandName, (cmd, callback) -> {
Object ret = method.invoke(handler, cmd);
if (callback != null && ret != null) {
callback.callBack(ret);
}
});
}
});
});
}
呼び出すときは、コマンド名を使ってハンドラーを見つける。実行後、結果はCallBackによって呼び出し元に返される。
CommandExecutor
public void execute(Command cmd, CallBack callBack) {
try {
context.get(cmd.getClass().getName()).handler(cmd, callBack);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
呼び出し側のコードは以下のようになる。
MainView
// Commandを作成し
ApplicationCommand applicationCommand = new ApplicationCommand(application);
// 実行して
commandExecutor.execute(applicationCommand, (CallBack<UnderWritingResult>) result -> {
// CallBack
if (result.isPass()) {
doSomething...
} else {
doSomething...
}
});
注入されたさまざまなサービスは、汎用コンポーネントで置き換えられる。
MainView
@Route("/main")
class MainView extends VerticalLayout {
private CommandExecutor commandExecutor;
// 【消えた】private SalesService salesService;
// 【消えた】private ProductService productService;
// 【消えた】private UnderwritingService underwritingService;
// 【消えた】private ApplicationRecordRepository repository;
MainView(CommandExecutor commandExecutor) {
this.commandExecutor = commandExecutor;
以上です。
Source Code
終わり
誰も[いいね]をくれないので、ちょっと休んでまた書くよ。