概要 / 説明
ことりんと一緒 Springもね - 7. サービス では、単一モジュールに責務が集中する事が避けるような改修をおこないました。
理由は、保守性や拡張性、また可読性の低い Fat Controller になる事を避けるためで、そのために Controller 層からアプリケーションロジックを分離し、 Service 層を設けました。
これにより、Controller 層と Service 層でそれぞれフォーカスする機能領域を分離する事ができるようになります。
レイヤ | 役割 |
---|---|
Controller 層 | リクエストの受付け |
Service 層 | リクエスト受付け以外のビジネスロジックの処理 |
さて新規に設けた、Service 層の責務について考えてみます。
Service 層の役割としては、リクエストの受付け以外の
ビジネスロジックの処理としました。
つまり、このままでは、ビジネスロジックの結果の永続化処理もこの Sevice 層で行うことになります。
ところで、データの永続化を考えてみると、永続化する対象領域を Service 層で意識すると、
対象領域の種類に応じた設計する必要があります。
また、外部領域の種類に依存関係が強くなり、変更をする際に影響範囲が大きくなってしまいます。
例えば、データベースを例に考えると、Oracle DB から MySQL に変更をすると、その方言の違いを意識した設計が必要になります。
Service 層の役割である ビジネスロジックの処理 から考えると、結果の永続化先は意識しないで、透過的に処理ができた方が設計がシンプルになり、保守性も向上します。
この Service 層から永続化処理を分離するための仕組みとして Repository 層を設けます。
前提 / 環境
ランタイムバージョン
- Kotlin : 1.3.11
- SpringBoot : 2.1.1.RELEASE
Spring Dependencies
- Web
- Actuator
- JDBC
- JPA
開発環境
- OS : Mac
- IDE : IntelliJ IDEA
- Build : Gradle
- DB : H2 Database
Repository 層を設けるに際して、今回ここでは永続化領域としてデータベースを対象にします。
また、そのデータベースの種類は簡略化のためH2データベースを使用し、組み込みモードで実行することにします。
手順 / 解説
Dependency
データベースアクセスを行うため、以下の Dependency を build.gradle に追加します。
- org.springframework.boot:spring-boot-starter-jdbc
- org.springframework.boot:spring-boot-starter-data-jpa
また、H2 Database を利用するための以下の Dependency を追加します。
- com.h2database:h2
データベース定義
application.yml に以下のH2データベースに関する定義を追加します。
spring:
datasource:
tomcat:
test-on-borrow: true
validation-interval: 30000
validation-query: SELECT 1
remove-abandoned: true
remove-abandoned-timeout: 10000
log-abandoned: true
log-validation-errors: true
max-age: 1800000
max-active: 50
max-idle: 10
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:app;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=TRUE
username: guest
password: guest
h2:
console:
enabled: true
jpa:
hibernate:
ddl-auto: update
show-sql: true
Repository 層
永続化処理を行うレイヤを設けるために次のインターフェース定義を行いました。
interface MessageRepository : CrudRepository<Message, String>
このインターフェースを介してデータの永続化を行うエンティティの操作を行うようにします。
Service 層
Service 層からは インジェクションしたRepository層のインスタンスを介して、エンティティの操作を行います。
@Autowired
lateinit var repository: MessageRepository
fun getMessages() : Iterable<MessageDTO> = repository.findAll().map { it -> MessageDTO(it) }
fun insertMessage(messageDto: MessageDTO) = MessageDTO(
repository.save(Message(
title = messageDto.title,
message = messageDto.message
))
)
まとめ / 振り返り
Controller / Service / Repository として役割を分離して設計する事によりコードが分離前と比べて随分とシンプルになった事がわかると思います。
このような設計を行っておく事でモジュールの肥大化やスパゲッティコード化、また拡張性の低下を抑止する事ができます。