Spring BootでModelMapperにカスタムのConverterを楽に追加できるようにする

# 概要
ModelMapperを使うとObject間の変換ロジックをそこに集約できて便利です。
[こちらの記事](https://qiita.com/euledge/items/482a113589015590cf19) がModelMapperのメリットをわかりやすく説明しています。
ModelMapperは変換元と変換先のフィールド名が同じなら`modelMapper.map(input, Output.class)`とすれば変換できますが、フィールド名や型が違うときはカスタムのConverterを追加する必要があります ([参考記事](https://qiita.com/syany/items/dab8b0d6543ae82ca77a))。
カスタムのConverterを作ったときにわざわざ` modelMapper.addConverter(newConverter)`とするのが大変なのでしなくて良い方法を提示します。

# 動作確認環境
https://github.com/yoshikipom/runlog-api/blob/master/build.gradle

# 対応
## ModelMapperの登録
build.gradleに以下を追加
`implementation 'org.modelmapper:modelmapper:2.3.7'`

以下でModelMapperをSpringのアプリケーションへDIする。
このとき、`List<Converter<?, ?>>`をDIしているのがポイント。Spring BootはListやMapであるクラスをDIすることができる([参考記事](https://qiita.com/gagagaga_dev/items/c16e5b6b3dff6df7e406))。
ListでDIしたConverterクラス(後述)をすべてModelMapperへ登録する。

```
import java.util.List;
import org.modelmapper.Converter;
import org.modelmapper.ModelMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppBean {

  private List<Converter<?, ?>> converters;

  public AppBean(List<Converter<?, ?>> converters) {
    this.converters = converters;
  }

  @Bean
  public ModelMapper modelMapper() {
    var modelMapper = new ModelMapper();
    for (Converter<?, ?> converter : converters) {
      modelMapper.addConverter(converter);
    }
    return modelMapper;
  }
}
```
実装例: https://github.com/yoshikipom/runlog-api/blob/451979c464e2de085ce4d603f5422e70e8d66dc3/src/main/java/jp/yoshikipom/runlogapi/AppBean.java

## Conveterの登録

ここのクラスが複数になる想定。
`org.modelmapper.AbstractConverter` のConvertメソッドを実装すればよい。型引数に`<Inputクラス, Outputクラス>`を入れる。
ModelMapperの登録部分へDIする必要があるため@Componentをつけるのを忘れないこと。

```
import org.modelmapper.AbstractConverter;
import org.springframework.stereotype.Component;

@Component
public class SomethingConverter extends AbstractConverter<Inout, Output> {

  @Override
  protected Output convert(Input source) {
    // your conversion rules
    return output;
  }
}
```
実装例: https://github.com/yoshikipom/runlog-api/blob/master/src/main/java/jp/yoshikipom/runlogapi/infra/record/RecordEntityConverter.java

## 呼び出しかた

ここまでくると呼び出せる。

```
@Service 
public class SomethingService {

  private final ModelMapper modelMapper;

  public SomethingService(ModelMapper modelMapper) {
    this.modelMapper = modelMapper;
  }

  public Output method() {
    // something
    return modelMapper.map(input, Output.class))
  }
```
実装例: https://github.com/yoshikipom/runlog-api/blob/master/src/main/java/jp/yoshikipom/runlogapi/infra/record/RecordRepoImpl.java

## (おまけ)テスト

楽に追加する話とはあまり関係がないがModelMapperを使うことで変換ロジックが隠蔽されるためテストもシンプルになる。

```
@SpringBootTest
class SomethingServiceTest {

  @Autowired
  private SomethingService target;
  @MockBean
  private ModelMapper modelMapper;

  @Mock
  private Input input;
  @Mock
  private Output output;

  @BeforeEach
  void setUp() {
    when(modelMapper.map(input, Output.class)).thenReturn(output);
  }

  @Test
  void test_success() {
    // something
    var actual = target.method();
    assertEquals(output, actual);
  }
}
```
実装例: https://github.com/yoshikipom/runlog-api/blob/master/src/test/java/jp/yoshikipom/runlogapi/infra/record/RecordRepoImplTest.java

# 参考
- https://qiita.com/euledge/items/482a113589015590cf19
- https://qiita.com/syany/items/dab8b0d6543ae82ca77a
- https://qiita.com/gagagaga_dev/items/c16e5b6b3dff6df7e406