読者です 読者をやめる 読者になる 読者になる

1年目による1年目のためのMySQLチューニング手順

MySQL

MySQL version : 5.5.38

目次

  1. 前書き
  2. 遅いクエリの見つけ方
  3. 解決策の決定
  4. チューニングの方法
    1. クエリの改善
    2. INDEX
    3. パーティション
    4. コマンドの大文字小文字
  5. おわりに


1. 前書き

こんにちは、入社1年目の紺野です。
入社後、予約TOP10チームにJOINしてから、MySQL関連の改修を任されることが結構ありました。大学ではPostgreSQLを習っていたのですが、実務として取り組むと、ただ受け身でやっている時よりも知識のUPDATEが早いですね。
今回は1年目の自分が取り組んできた、遅いクエリの見つけ方から改善までの手順と解決法をSELECTしましたので、記事として書かせて頂きます。
どうでもいい前置きはここまで本題に入りましょう。


2. 遅いクエリの見つけ方

MySQLにはスロークエリログという、結果が返ってくるのが遅いクエリをログとして残してくれる機能が備わっています。まずはこのログを出力する設定をして、チューニングすべきクエリを洗い出しましょう。

スロークエリログの設定がどうなっているかは、MySQLコンソールからSHOW VARIABLESコマンドで確認できます。

mysql> SHOW VARIABLES LIKE 'slow_query%';
+---------------------+-------------------------------+
| Variable_name       | Value                         |
+---------------------+-------------------------------+
| slow_query_log      | OFF                           |
| slow_query_log_file | /var/log/mysql/mysql-slow.log |
+---------------------+-------------------------------+
2 rows in set (0.00 sec)

mysql> SHOW VARIABLES LIKE 'long%';
+-----------------+-----------+
| Variable_name   | Value     |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+
1 row in set (0.00 sec)
  • slow_query_log
    ログを出力するか否かの設定です。OFFになっていますのでスロークエリログは吐き出されない状態です。
  • slow_query_log_file
    ログが書き込まれるファイルの場所です。
  • long_query_time
    この値を超えると遅いクエリとして判断され、ログに出力されます。単位は秒です。

スロークエリログの設定

コンソールから設定
コンソールからは、以下のコマンドを入力することにより設定できます。

mysql> SET GLOBAL slow_query_log_file = '/var/log/mysql/mysql-slow.log';
mysql> SET GLOBAL long_query_time = 1;
mysql> SET GLOBAL slow_query_log = ON;

my.cnfから設定
設定ファイルからは、rootユーザーで編集します。

# vi /etc/my.cnf

以下を追記

# slow query
##
slow_query_log
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 1

これでlong_query_timeで設定した値以上の時間がかかったクエリをログとして残してくれます。

スロークエリログの見方

実際にログを見ていきましょう。

# less /var/log/mysql/mysql-slow.log
# Time: 170118 16:51:22
# User@Host: root[root] @ localhost []
# Query_time: 21.486755  Lock_time: 0.000033 Rows_sent: 10057  Rows_examined: 2256448
SET timestamp=1484725882;
SELECT t1.id, t1.fiald1, t2.fiald1 FROM table1 t1 INNER JOIN table2 t2 ON t1.id = t2.table1_id WHERE t2.fiald1 IS NOT NULL ORDER BY t2.fiald1;
※実際のDBの内容とはもちろん異なります
  • Time
    クエリが実行された日時です。この場合は2017年1月18日 16:51:22ですね。
  • User@Host
    ユーザーIDとリクエストした端末です。ここを見ればどのサーバーでクエリがたたかれたかが分かります。
  • Query_time
    クエリの結果が帰ってくるまでの時間です。先程設定したlong_query_timeの値以上の数値になっているはずです。21秒ってやばいですね。21秒間ページが表示されなければ一般的にユーザーはキレます。
  • Lock_time
    ロックされた時間です。ロックとは、異なるセッションによる競合を防ぐため、他のクエリは実行されないような状態になっていることです。UPDATEINSERT実行中にSELECTなどしてしまうと正確な結果でなくなってしまいます。
  • Rows_sent
    クエリにヒットした件数です。
  • Rows_examined
    処理対象となった行数です。
  • SQL文
    実行されたクエリです。これが改善すべきクエリということになります。


3. 解決策の決定

EXPLAINコマンドを使って調べていきましょう。このコマンドはクエリの先頭につけることによって、どういった手順でデータを取得するかの実行計画を確認できます。実際にクエリがたたかれ、結果が返ってくる訳ではないので安心して使ってください。
ただし、サブクエリのみ実行してみないと見積もりができないようで、重いサブクエリを使うとEXPLAINも返ってくるのが遅くなります。

実行例

f:id:AdwaysEngineerBlog:20170120181349p:plain

どういった項目をチェックしてチューニング方法を練るかを先に申しますと、以下のようになります。

①最初にデータを取ってくるテーブルのrowsを減らせそうか
②インデックスが使われているか否か
③ExtraにUsing filesortやUsing temporaryが入っていてそれを取り除けないか

ではこのチェック項目の判断を下すうえで必要な情報の説明をしていきましょう。

  • idとselect_type
    この2つはクエリ内での実行順を表しています。JOINのみから構成されるクエリの場合単純に上から順に実行されますが、サブクエリ、UNIONなどが絡んでくる場合は順番が変わってきます。最初に取得するデータ量(後述するrowsの数)が少ないほどクエリ全体の処理が早くなるため、実行順を理解することはチューニングの手助けになるでしょう。

  • table
    対象のテーブルを表します。

  • type
    かなり端折って説明すると、インデックス使っているか否か、インデックスをどう使っているかを表す項目です。

    • const
      PRIMARY KEYまたはユニークインデックスによる等価検索(例:WHERE id = 1)。最速。
    • eq_ref
      JOINONにおいてPRIMARY KEYまたはユニークインデックスが使われている状態。早い。
    • ref
      ユニークでないインデックスによる等価検索(例:WHERE age = 18)。いい感じに早い。
    • range
      インデックスによる範囲検索(例:WHERE age BETWEEN 13 AND 15)。わりと早い。
    • index
      indexとあるので早そうですが、これはインデックス全体をスキャンしているため、まぁまぁ早い程度です。
    • ALL
      インデックスを全く使わず、テーブル全体をスキャンするため最遅。改善すべき。
  • possible_keys
    使用する候補として挙げられたキーやインデックスです。

  • key
    どのキーやインデックスが使われたか。貼ったインデックスが実際に活用されているかがわかります。

  • key_len
    使われたキーの長さ。キーの長さが短いほど高速なのでインデックスをつけるカラムを選ぶ時などは注意してください。

  • rows
    そのテーブルから抽出したカラム数の大まかな値。EXPLAINは実際に実行するわけではないので予測数を出してくれます。また、この値はWHEREを適用する前なので、検索条件を設定している場合の実行結果はおおよそこれより少なくなります。ただ、先程書いたようにサブクエリは実際に実行してみないと結果の数がわからないため、この場合は正確な値が出てきます。

  • Extra
    クエリを実行するために使用する方法がここに記載されます。数が多いのでよく見るものをここでは説明します。

    • Using where
      WHEREを使っており、インデックスを使っただけでは絞り込めない場合にでます。
    • Using index
      インデックスだけをもって絞りこみ、ソートできている場合にでます。かなり早いはずです。
    • Using filesort
      テーブルからデータを取ってきた後にクイックソートを使ってソートしている場合にでます。クイックソートとかいう名前ですが取ってきたデータが多い場合、ソートに時間がかかるため殆どの場合遅いです。
    • Using temporary
      結果をソートしたりするのにテンポラリテーブル(仮のテーブル)を作る場合に出ます。Using filesortと同じく遅くなることが多いので注意が必要です。


4. チューニングの方法

さあいよいよチューニングに入っていきます。
EXPLAINで表示されたrowsを減らすことを目標とし、以下のような改修を加えましょう。

1. クエリの改善

JOINやサブクエリ、UNIONを使っている場合は、それぞれ対応するテーブルからデータを取ってくる順番が決まります。その時重要なのが、最初に取ってきたデータの量です。このデータ量が多いと、次に取ってくるデータとマージするときに見る行が増えるため実行速度が落ちます。
10万件のユーザーテーブルと100万件の予約テーブルをINNER JOINする時、予約テーブルから始めるよりもユーザーテーブルから始める方が早くなるでしょう。

2. INDEXを貼る

EXPLAINした時に、keyNULLだったり、WHEREで指定した条件のカラムにINDEXが貼られていない場合は検討しましょう。WHERE の他に、JOINの条件で指定されているカラムにも貼ると効果が出ます。

今までさんざん出てきたINDEXですが、とりあえず貼りまくれば良いというわけではありません。INDEXを貼ったカラムはメモリ上にのるため、貼りすぎるとMySQL以外のプロセスを圧迫し、メモリが溢れてしまう危険があります。よって、本当に使うものにのみINDEXを貼るようにしましょう。

3. パーティション

データ量が膨大になると、クエリの改善やINDEXを貼っても遅いときがあります。そんな時は、検索条件のカラムのパーティションを切ると良いでしょう。

4. コマンドの大文字小文字

MySQLはクエリの実行手順としてまずクエリの読み込みをし、その時コマンドが小文字で入力されていた場合、それを大文字に置き換える処理が入ります。なのでこの処理を削るため、SQLのコマンドは大文字で入力しましょう。とはいっても今のコンピュータの性能からすると無視しても問題ない程度らしいのでおまじない程度に考えておいてください。ECサイトの表示速度が1秒早くなっただけで売上1200億円上がったなんて話もあるので0.0000001秒早くなれば年1万円くらいの収入アップになるでしょう。

結果

チューニングを試してみた結果、rowsは減りましたでしょうか。そうしたら実際にクエリを叩いて実行速度を比べてみましょう。早くなってたら良いですね。

ただここで一つ注意してほしいことがあります。
MySQLではクエリの実行結果をキャッシュしておく機能があるので、同じクエリを叩くと0.0秒で返ってきます。クエリのテストは何回か叩くと思うので、いきなり早くなってワロタと混乱することを防ぐためにSQL_NO_CACHEを使いましょう。

SELECT SQL_NO_CACHE t1.id, t1.fiald1, t2.fiald1
  FROM table1 t1 INNER JOIN table2 t2 ON t1.id = t2.table1_id




5. おわりに

いかがだったでしょうか。初めてのエンジニアブログだったので要領を得ていないかもしれませんが、少しでも同胞の助けになればと思います。

新年なのでAWSのリソースだけで1年の目標管理マイクロサービスを作ったお話

AWS

久保田です。

2017年が始まりましたね。。。
早いもので今年の4月で3年目、今年はますます頑張りたいなと思います。

僕は毎年、年始に意識高く目標を立てているのですが、
だいたい3月くらいには忘れてしまっています。笑

なので今年は忘れないよう、目標管理をしてくれるマイクロサービス的なシステムを作ったので、書きたいと思います。
ぜひ真似してください。

今回はAWSのサービスのみで構築しました。構成は以下のような感じです。

f:id:AdwaysEngineerBlog:20170113150018p:plain

そういえばAWS、コンソール画面変わってましたね。これで伝わるのかな。。。

CloudWatchのスケジュール機能でLambdaの関数を動かし、3ヶ月に一度目標のメールを自分に送ります。
送られてくるメールは以下の画像のようなイメージです。

f:id:AdwaysEngineerBlog:20170113150108p:plain

そしてメールには目標とそれぞれの目標に対応したURLがあり、そのURLをクリックするとAPI GateWayにリクエストが送信され、Lambdaが動き、S3にある目標データが入っているjsonファイルを書き換えます。さらに目標が全て達成されたら、CloudWatchのスケジュールをdisableにします。

では早速作っていきます。

S3に目標データを作る

まずはS3にデータを置いておきます。
今年の目標を意識高く掲げておきましょう。

  • goal.json
{
  "1": "腹筋を割る",
  "2": "引っ越す"
}

このファイルをS3のどこかにuploadしておきます。

Lambda関数を作っておく

先にLambdaの関数を作っておきます。
先に作っておかないとCloudWatchのスケジュールが登録できないためです。

今回は、 S3から目標ファイルを読み込みメールを送る関数と、
API GatewayにリクエストがきたらS3の目標ファイルを更新する関数
の2つが必要となります。

適当に名前をつけてください。
僕は メールを送る関数を 「sendNotify」
更新する関数を 「clear」
としました。

実装はまた後で行います。

CloudWatchにスケジュールを登録する

次にCloudWatchにスケジュール登録します。

左ペインからイベントの下にある ルール を選び、以下のCron式を登録します。

0 10 1 3,6,9,12 ? *

そしてこのスケジュールに紐付けるLambda関数を選択します。

f:id:AdwaysEngineerBlog:20170113150131p:plain

API Gatewayでエンドポイントの作成

API Gatewayを使ってエンドポイントを作成します。
このエンドポイントにリクエストがきたらS3の目標ファイルを更新します。

今回は目標が達成されたら対象のデータを消すので、DELETEメソッドで作ります。
そして消す目標のIDをパスパラメータで受け取るようにします。

なのでリソースとメソッドは以下の画像のようになります。
(※目標更新の方のLambda関数を紐付けておいてください。)
(※ GETがある理由は後でわかります。)

f:id:AdwaysEngineerBlog:20170113151940p:plain

そしてマッピングテンプレートを設定し、パスパラメータをLambdaが受け取れるようにします。

f:id:AdwaysEngineerBlog:20170113150250p:plain

そしてDELETEなので、ステータスコードは204を返すようにしておきます。

メソッドレスポンスと統合レスポンスを編集します。

f:id:AdwaysEngineerBlog:20170113150231p:plain

f:id:AdwaysEngineerBlog:20170113150214p:plain

そしてデプロイしておきます。
デプロイしたら得られるエンドポイントをメモっておいてください。

これでDELETEメソッドは完成です。

次にGETメソッドの設定をしていきます。
なぜかというと、メールからURLをクリックした時に発行されるリクエストのメソッドはGETだからです。
なので、このGETを通って上で作ったDELETEメソッドに流します。

このようにしておきます。

f:id:AdwaysEngineerBlog:20170113151801p:plain

エンドポイントURLのところに上でメモしておいたエンドポイントを入れてください。

SNSの設定

Lambdaからメールを送る必要があるので、SNSにトピックを作ります。
ここも名前はなんでもいいです。僕は「goalNotify」としました。

購読の設定まで完了させておきます。

Lambda関数の実装

ここまでで型が完成したので、中身のLambda関数を作っていきます。
僕はPythonを使いました。

まずはS3から目標ファイルを読み込みメールを送る関数です。

import boto3
import json
import collections

BUCKET = 'バケット名'
KEY    = 'ファイル名'

def lambda_handler(event, context):
    # S3からファイルの読み込み、jsonからdict型のオブジェクトの生成
    s3 = boto3.client('s3')
    obj = s3.get_object(Bucket=BUCKET, Key=KEY)
    decoder = json.JSONDecoder(object_pairs_hook=collections.OrderedDict)
    jsonObj = decoder.decode(obj['Body'].read())
    

    # メール本文作成
    message = ""
    for k, v in jsonObj.iteritems():
        message += v + "\n" + " if you clear this goal, click under link. \n ここにエンドポイント"  + k + "\n\n"
    
    # SNSからトピックのarnを取得  
    sns = boto3.resource('sns')
    topic = sns.create_topic(Name="トピック名").arn
    # メール送信
    sns.Topic(topic).publish(
        Subject="goal of 2017.",
        Message=message,
    )


    return

次にAPI GatewayにリクエストがきたらS3の目標ファイルを更新する関数です。

import boto3
import json
import collections

BUCKET = 'バケット名'
KEY    = 'ファイル名'

def lambda_handler(event, context):
    # S3からファイルの読み込み、jsonからdict型のオブジェクトの生成
    s3 = boto3.client('s3')
    obj = s3.get_object(Bucket=BUCKET, Key=KEY)
    decoder = json.JSONDecoder(object_pairs_hook=collections.OrderedDict)
    jsonObj = decoder.decode(obj['Body'].read())
    
    # 対応するgoalIdがあったら消して更新
    if event['goalId'] in jsonObj:
        del jsonObj[event['goalId']]
        jsonStr = json.dumps(jsonObj)
        s3.put_object(Bucket=BUCKET, Key=KEY, Body=jsonStr)
        
        # もし全て完了したら、スケジュールを止める。
        if len(jsonObj) == 0:
            cloudwatch = boto3.client('events')
            cloudwatch.disable_rule(
            Name='sendNotify'
        )

        
    return

これで完成です。 試したい方はS3から目標ファイルを読み込みメールを送る関数を動かしてみてください。

さて、あとは3ヶ月後1つでも押せるように日々努力していくだけ、、、ですね!

今回は以上です。今年もよろしくお願いいたします。

新年明けましておめでとうございます

f:id:AdwaysEngineerBlog:20161227144327j:plain

謹んで新年のお慶びを申し上げます。

昨年は大変お世話になりありがとうございました。
本年も昨年同様よろしくお願い申し上げます。
皆様のご健康とご多幸を心よりお祈り申し上げます。

平成29年元日

株式会社アドウェイズ
サービスデベロップメントグループ一同

すげーロゴ

Ruby

こんにちは、久保田です。

2016年も終わりですね。。。

私達エンジニアブログもこの投稿で今年最後の更新とさせていただきます。

今回はちょっと変わったプログラムを作りました。

こちらはアドウェイズのロゴです。

f:id:AdwaysEngineerBlog:20161118192801g:plain

これをRubyのプログラムにしてみました。

gist81462828b3e6db3179d2c5e8285fa575

そしてこれを動かすと、、、

f:id:AdwaysEngineerBlog:20161227141919p:plain:w300

アドウェイズのスローガンが出力されます。

gist埋め込みにしたらなんか縦長になっちゃいましたが、、、 コンソールで見るとこんな感じです。

f:id:AdwaysEngineerBlog:20161227142754p:plain:w300

会社のロゴをかたどったプログラムを実行すると会社のスローガンが出力されるというおしゃれプログラムです。

unicodeの該当するコードポイントを出したり、shift-jisのコードポイントを計算したり、2進数から文字列になんやかんやしたりしてます。笑

ぜひコピペして実行してみてください!

ではよいお年をお過ごしください。
今年もありがとうございました。🐵

Scalaでマイクロサービス化を進めるために考えたこと

Adways Advent Calendar 16日目の記事です。

http://blog.engineer.adways.net/entry/advent_calendar/archive


こんにちは、古川です。現在アドテクチームに所属しています。

まさか、まさか一番最後になるとは思っていませんでした。。。あまりでかいことを書ける自信は持ちあわせてませんが頑張って書いていきたいとおもいますのでよろしくお願いします。

アドウェイズのアドテクチームでは、Perlで運用してきましたが、現在Scalaで既存サービスに対してマイクロサービスを進めてきています。

それを実践するにあたって、出てきた課題をどのように改善したかについて共有したいと思います。

マイクロサービスで取り組むにあたって出てきた課題

ビジネスロジックがWebフレームワークや具体的な実装に縛られていた

いままでの既存サービスはPerlのCatalystであり、MVCの考えで実装されていましたが、ビジネスロジックがコントローラやモデルに相当する箇所に書かれていたりしました。

「コントローラにビジネスロジックが書かれている」にはフレームワークのものと密結合しているため、このような問題がありました。

  • テストコードが書けない
  • つぎのことをやろうとしたときにビジネスロジックにも影響が及んでしまう
    • フレームワークのアップデートや切り替え
    • Webアプリの機能の切り捨て

一方、「モデルにビジネスロジックが混ざって書かれている」ケースについては、ビジネスロジックがモデルと結びついているため、これらの問題がありました。

  • 使われ方と結びついているため、そのモデルが複雑になる
  • モデルのテストコードも複雑になる
  • モデルに関するライブラリのアップデートなどをしようとしたとき、ビジネスロジックにも影響が及んでしまう

両方に共通している問題点は、ビジネスロジックがフレームワークや具体的な実装に縛られているような書き方にあり、これを防ぎたいと感じていました。

この問題については、つぎの手段で解決を試みました。

ヘキサゴナルアーキテクチャーの考えをプロジェクトに採用

「実践ドメイン駆動設計」の本を読んでいったところ、4章あたりにある「ヘキサゴナルアーキテクチャー」というのを見つけました。「ヘキサゴナルアーキテクチャー」はビジネスロジックが書かれるドメインやそれ以外の実装と明確に分離できるようなモデルだったので、まずはこの考えを取り入れることを検討しました。

他社さんの実装事例がないか確認

まずは、他社さんがすでにアーキテクチャーで実装している事例がないかさがしました。

するとセプテーニさんのところで「ヘキサゴナルアーキテクチャー」でプロジェクト構成している事例を見つけました。

Scalaで学ぶヘキサゴナルアーキテクチャ実践入門

最初は、ここに使っているプロジェクトをそのまま採用しようとしましたが、以下のような事情があったためしませんでした。

  • ほとんどの人がPerlやMVCフレームワークしかさわっていなかったため
  • 今までの方式と異なるこの手法を教えるのには時間がかかる

上記を参考にプロジェクトにヘキサゴナルアーキテクチャーをどう落とし込むか考えました。

WebフレームワークなどのUIやバッチについて

WebフレームワークなどのUIやバッチのエンドポイントと、ビジネスロジックであるドメインはプロジェクト単位でわけるようにしました。

具体的にはつぎのようにわけました。

  • web:Webフレームワークのコントローラなどを管理するところ
  • batch名:バッチの処理を管理するところ
  • core:ビジネスロジックなどのドメインを含む

そして、core以外のプロジェクトにビジネスロジックを埋めないようにしています。

このような方法をとることで、Webフレームワークなどとビジネスロジックを分離させることにしました。

MySQLへのアクセスする処理などについて

MySQLへのアクセスなどの処理については一旦coreのなかで管理することにしましたが、アプリケーション層・ドメイン層・インフラ層の3層に分けることで、ビジネスロジックと別々に管理するようにしました。

  • アプリケーション層:(使い所がまだ理解していないものの)Webフレームワークとビジネスロジックの間で調整する役割
  • ドメイン層:ビジネスロジック全体
  • インフラ層:DBアクセスなどの具体的な実装

DIパターンを採用を検討

ここまでの方法だけだと、書き方によってドメイン層がインフラ層に依存してしまいます。

そこで「DIパターン」を使って、ドメイン層がインフラ層に直接依存させないよう試みました。

従来のcake patternについて

まずは、一番最初に開発したScalaプロジェクトでは、こちらのページのCake Patternを採用していました。

Cake Pattern を理解する - daimatz.net -

この方法はぱっと見が複雑だったので、採用しませんでした。

Google Juiceを使った動的DIについて

つぎに、Google Juiceを使った動的DIの実装も検討しましたが、こちらについてはこのような問題があり、採用しませんでした。

  • Google Juiceの使い方について学ばなければいけない
  • 書き方も色々あってどれを使えばいいかわかりづらい
  • DIの仕組みがライブラリに依存してしまうので、ライブラリのアップデートで影響を受けてしまう
Minimal Cake Patternについて

さいごにドワンゴが考えた「Minimal Cake Pattern」という実装の仕方について検討しましたが、今回はこちらを採用しました。

Scalaにおける最適なDependency Injectionの方法を考察する 〜なぜドワンゴアカウントシステムの生産性は高いのか〜

採用した理由としては、

  • 従来のcake patternと比べて「Mixin」や「継承」のみしか使っていなく見た目が比較的シンプルだった
  • Google Juiceのように特定のライブラリに依存することがなかった

と、他の手法と比べてデメリットが克服されていたと感じたからです。

この手法をドメイン層からインフラ層を呼び出す箇所で利用しました。

Scalaのマイクロサービス化でやるべきことが非常に多かった

1つ目のマイクロサービスがある程度でき、この考えを他のチームに展開しようとしましたが、

プロジェクトを作るだけでも考えることが非常に多いことに気づきました。

たとえば、

  • 環境設定はどこに配置すればよいのか?
  • DBの接続はどのようにするのか?
  • ログはどのように吐かせるのが望ましいか?
  • プロジェクトの分離はどのようにすればいいのか? などなど

を考慮しなければいけませんでした。

ほかにも、sbtやlogbackの初期の設定でハマっていました。

ここに時間が割かれることを避けたかったため、つぎの手段をとりました。

最初のマイクロサービス化で作成したプロジェクトをベースにテンプレートを作成

そもそもうちの会社では、テンプレートのようなプロジェクトを持っていませんでしたので、今回それを持つことでこの手間の省力化を図ろうとしました。

そこで最初のマイクロサービス化で作成したプロジェクトをベースに、不要な箇所を取り除くことでテンプレートを作成することにしました。

というのも、このプロジェクトではある程度アーキテクチャーが固まっていたからです。

今回つくったテンプレートについては、簡単な実装例も混ぜたものを公開します。もしよければ、ご参考ください。

※ 名前等は若干変更を加えていますが、問題なく動作はできます。

github.com

最後に

既存サービスのマイクロサービス化にあたって上記のことを取り組んだことで、少なくともテストはだいぶ書きやすくなりました。

「改修しやすいか」という面についてはまだまだこれからですので、今回作成したテンプレートを他のプロジェクトに投入したり、運用させてみたりすることで検証してみたいと考えています。

また、今回のことを通してDDDについても部分的ですが学ぶことができました。

今後は、ユビキタス言語とかドメインやコンテキストマッピングとかも学び・実践していけば、よりよいサービスが作れると感じていますので、そちらにも力を入れていこうかと思います。