KotlinでConstructor Injectionが増えてきたからDelegateをつかってリファクタリングしてみた

今回のエントリはDelegateの使い方をまとめる。
次のようなConstructor Injectionに複数のサービスクラスが並んだTaskBackendServerクラスがある。

@GRpcService
class TaskBackendServer(private val getTaskService: GetTaskService,
                        private val findTaskService: FindTaskService,
                        private val createTaskService: CreateTaskService,
                        private val updateTaskService: UpdateTaskService,
                        private val deleteTaskService: DeleteTaskService,
                        private val finishTaskService: FinishTaskService) : TaskServiceGrpc.TaskServiceImplBase() {

TaskBackendServerは複数のTaskServiceに依存していることが分かる。これくらいの数になると多くの依存が明確でり煩雑な印象を受ける。またテスト時にも依存クラスのインスタンスをつくりTaskBackendServerをつくりあげるのは骨が折れる。

そんなときには Delegate(委譲)をつかって処理を委譲させるとよい。
DelegateをつかえばAクラスにBクラスのpublicな関数を委譲させることができる。委譲されたAクラスはBクラスの関数を使えるようになる。AクラスはCクラス、Dクラスなど委譲させる関数名にコンフリクトがなければ複数のクラスを委譲することができる。

上記のコードではTaskにまつわる複数のサービスクラスが TaskBackendServerに依存しているので DelegateTaskServiceクラスをつくり委譲をしてみる。

委譲させるクラスはインターフェースを実装したクラスであること

委譲のコードの前にGetTaskServiceFindTaskServiceなどそれぞれの委譲させるクラスは次のようにインターフェースを実装したクラスであることを整理しておく。

interface CreateTaskService {
    fun createTask(command: CreateTaskCommand): Task
}

@Service("createTaskService")
class CreateTaskServiceImpl(private val taskRepository: TaskRepository) : CreateTaskService {

    @Transactional
    override fun createTask(command: CreateTaskCommand): Task {
        return taskRepository.create(command.title).fold({
            task -> task
        }, {
            error -> throw handle(error)
        })
   }
}

DelegateTaskServiceをつくる

@Service
class DelegateTaskService(private val getTaskService: GetTaskService,
                          private val findTaskService: FindTaskService,
                          private val createTaskService: CreateTaskService,
                          private val updateTaskService: UpdateTaskService,
                          private val deleteTaskService: DeleteTaskService,
                          private val finishTaskService: FinishTaskService) :
        GetTaskService by getTaskService,
        FindTaskService by findTaskService,
        CreateTaskService by createTaskService,
        UpdateTaskService by updateTaskService,
        DeleteTaskService by deleteTaskService,
        FinishTaskService by finishTaskService

委譲させるにはクラス名の宣言のあとに、 byキーワードをつかいDelegateを明示する。

TaskBackendServerのConstructor Injectionは次のようにスッキリした。

@GRpcService
class TaskBackendServer(private val delegateTaskService: DelegateTaskService) : TaskServiceGrpc.TaskServiceImplBase() {

また、delegateTaskServiceGetTaskServiceの関数を呼び出せるようになった。

val task = delegateTaskService.getTask(GetTaskCommand(taskId.toLong()))

まとめ

  • Delegate(委譲)についてまとめた。
  • 複数のインターフェースを実装する場合、委譲を使えばクラスの肥大化を抑えることができる。
  • 今回のようにConstructor Injectionが増えてきて依存度が増しと感じれば委譲を使い整理することができる。

コード

githubにコードがありますので合わせて確認できます。

github.com