Clean Architecture, Clean Life

仕事・個人での技術的なことつぶやきます

【GithubActions】composite run steps試してみたけどいまいちだった

経緯

GitlabCI→GithubActionsへの移行作業しているとき、Gitlabだとextendsっていう機能があって、Jobのテンプレートみたいなものが作れたんですよね

.tests:
  script: rake test
  stage: test
  only:
    refs:
      - branches

rspec:
  extends: .tests
  script: rake rspec
  only:
    variables:
      - $RSPEC

こんな感じでかけます(公式より)

Githubでもないかなーと探していたら、同じ機能ではないけど似たような機能として「composite run steps」っていうものを見つけたので試してみることに

結論

現状非対応のものが多く、あまり使い勝手の良いものではないなーという印象でした

非対応のものが多い

公式によると、

This feature does not support at the run step level:

  • timeout-minutes
  • secrets
  • conditionals (needs, if, etc.)
  • continue-on-error

とあります

secretが非対応なのは少し辛いですね

APKの署名処理とかでキー情報を使いたいケースがあるので

入り組んだファイル構成になってて書きづらい

公式によるとこちらのようなフォルダ構成にする必要があるようです

github.blog

別のファイルにあるだけでも可読性に欠けるからあまり嬉しくないのに、ちょっとビルドコマンド叩きたいがためだけにあまりやりたくないなーと

めちゃくちゃ複雑なことするor1ファイルにまとめると分岐がややこしくて行数がかなりかさんでしまうというような場合には有効かもしれません

正直そこまで複雑な処理をするのでなければ、同じファイルの中で分岐させたほうが行数は増えるけど何やってるかは追いやすくなるのでいいと思う

もし使われる方はこちらの注意点も参考にしていただけたらと思います

blog.n-z.jp

今後のアップデートに期待ですね

1ファイルにまとめる方法もまた書けたらいいなと思います

では読んででいただきありがとうございました

2021/09/09 追記

github.com

usesが使えるようになったようです

これでだいぶ使いやすくなったのではないでしょうか

さらにまだ対応していないものについても今後対応していくみたいです

github.com

Secretsが使えるようになったら再検討しようと思います

【Android】【Gradle】ライブラリ間の依存関係に関して

経緯

ローカルからAARファイルを読み込む際に、謎のエラーが発生したのでこちらに備忘録として記載しておく

結論

1.maven経由時はimplement時にtransitiveオプションを付けることで依存関係を解決できる

2.ローカルからAARファイルを読み込む場合は依存関係を含んでいないので自力で解決する必要がある

順番に解説します

maven経由時はimplement時にtransitiveオプションを付けることで依存関係を解決できる

これはそのままです

ライブラリ内の依存関係に関しては下記のようにtransitiveオプションを付けることで解決できます

implement ('com.example.sdk.android:1.0.0@aar') {
    transitive = true;
}

実際に、

「プロジェクトではRxJava3のライブラリ使っていたんですが、別のライブラリではRxJava2をつかっていた」

ということがありました

基本的にはライブラリの参照元でこういうふうに書いてね、と記載があるのでそれに従っていれば問題ないと思います (つまり私は見逃していたということ・・・)

問題は次です

ローカルからAARファイルを読み込む場合は依存関係を含んでいないので自力で解決する必要がある

その名の通り、ローカルの場合はtransitiveオプションが使えません

解決策としては

  • 提供元にMaven Repositoryを立ち上げてもらうように依頼する
  • すべての依存関係を洗い出す

の2つになるかと思います

自分で作ったライブラリなら特に問題はない(とはいっても、きちんと管理しておくに越したことはないけど)とは思いますが、外部から提供してもらったものに関してはややこしいですねー

Maven経由でできませんか?と依頼して、だめって言われたらせめて依存関係くらいは~(お通並感)と依頼してみるしかないと思われます

それすらしてくらないなら、ライブラリの選定から見直したほうがいいんじゃないですかね?

正直ライブラリが更新されるたびに依存関係見直すとかやってられないと思うので

では、同じ問題にぶち当たってる方、ご検討を祈ります()

参考記事:

stackoverflow.com

stackoverflow.com

【Github Actions】Githubのビルド番号を任意の値からカウントする

経緯

今関わってるプロジェクトのソースコード管理をGitlabからGithub移行する事になった

ソースコードだけではなく、CI(自動ビルド)もやっていて、合わせて移行することになったのだが、ここで一つ問題が発生した

gradleに記載しているversioncodeのパラメータを、GitlabCIのビルド時にversioncodeをジョブIDに上書きする処理を入れて入れていた

これ自体はgradleのtaskなので問題なく、Githubに移行後もそのまま使えるのだが、ジョブIDは連番のため、Githubに移行するとまた1からのカウントになってします

つまり「Githubに移行することによってバージョンがリセットされる」ということ

現状古いバージョンに上書きすることはできず(ローカルでやる場合は可能だが)、GithubでもCIを行うためには、GitlabのジョブIDを引き継ぐ必要がある

解決策

gradleのtaskの中で計算するしかないか、めんどくさいなー、と思った矢先、いいライブラリを発見した

github.com

使い方はかんたん

  1. Tagを打ってPushする
git tag build-number-500
git push origin build-number-500

※ 500は初期値にしたい値に変えれば良い

  1. Actionsに導入する
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - name: Generate build number
      id: buildnumber
      uses: einaregilsson/build-number@v3 
      with:
        token: ${{secrets.github_token}}        
    
    # Now you can pass ${{ steps.buildnumber.outputs.build_number }} to the next steps.
    - name: Another step as an example
      uses: actions/hello-world-docker-action@v1
      with:
        who-to-greet: ${{ steps.buildnumber.outputs.build_number }}

「Generate build number」のジョブで「einaregilsson/build-number@v3 」を使うことで、ビルド番号を取得できる その際にTagを打っている場合はそのTagに記載している番号からスタートする (上記の例では500を打っているので、501からスタートすることになる)

※ ちなみにTagが打たれていない場合は、自動的に「1」のTagが打たれるみたい

取得したビルド番号を使うには${{ steps.buildnumber.outputs.build_number }}でおk

すごいかんたん

今回は同じJob内の別Stepで使用する方法を記載しているが、リンク先には別Jobに引き渡す方法や、複数のビルド番号を使う方法とかも書いてある

まとめ

今の時代、自分がぶち当たってる問題は先人の誰かが必ず通ってる道で、その中のすごい先人様が道標を用意してくれてることが多いんだなと実感しました

今はありがたく使わせていただきますが、いずれはこういうのを作れる側になりたいな

【Mac】Touch Bar上にファンクションキーを長押しせずに、「F1」〜「F12」キーを常時表示する

経緯

Mac-AndroidStudioで開発しているときによくショートカットを使うんですが、Touch Bar搭載機種に変えてからfnキー長押ししないと「F○キー」が出てこなくなってすごい使いづらいんだけど、設定を変えたら常時表示できるって先輩に聞いたので試してみることに

方法

とはいっても、全てはここに書いています!笑

  1. Apple メニュー  >「システム環境設定」の順に選択し、「キーボード」をクリックします。
  2. ウインドウの上部で「ショートカット」をクリックし、左側のリストから「ファンクションキー」を選択します。
  3. 追加ボタン (+) をクリックし、App を選択して「追加」をクリックします。追加した各 App を使っている間は自動的に、Touch Bar にファンクションキーが常時表示されるようになります。

f:id:urasaku77:20210721164242p:plain

まとめ

かなり快適になりました Android開発以外にも、他にFキー使うアプリがあったら随時追加したいと思います

【Android】【Kotlin】コード上で機器のIMEIを取得する

経緯

今関わっているプロダクトで、トラックID的にIMEIを取得したいケースが出てきて調べたが、あまりまとまっていなかったので備忘録として

下準備

まずは権限が必要なので、マニフェストファイルに記載

<uses-permission android:name="android.permission.READ_PHONE_STATE" />

Activityに書く場合

普通にMainActivityに書く場合は簡単 onCreateあたりにこいつをぶち込む

val telephonyManager = this.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
val imei = telephonyManager.imei

ただこれだけだと、パーミッションのチェックができてない!って怒られてしまいます

なんで、今回はパーミッションが許可された際のみ取得するように記載

if (ActivityCompat.checkSelfPermission(
            this,
            Manifest.permission.READ_PHONE_STATE
        ) == PackageManager.PERMISSION_GRANTED
        ) {
            val telephonyManager = this.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
            imei = telephonyManager.imei
        }

Fragmentに書く場合

このケースになるといきなり難易度が増します getSystemServiceを呼ぶにはContextが必要ですが、Fragmentからは直接呼ぶことができません

少し雑なやり方をすると、fragmentでは呼ばれたActivityを取ることができるので、それを使って

if (ActivityCompat.checkSelfPermission(
            activity!!,
            Manifest.permission.READ_PHONE_STATE
        ) == PackageManager.PERMISSION_GRANTED
        ) {
            val telephonyManager = activity!!.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
            imei = telephonyManager.imei
        }

と書くこともできる (調べるとgetActivity()がいっぱい出てきますが、今はactivityだけでいけるようです)

でも!!はあまり使いたくないですね もしかしたら予期してなかったところから呼ばれる可能性もあります

その場合はFragmentからApplicationContextを参照できるようにすればいいです

参照

qiita.com

こちらを使うことで、このように書くことができます

if (ActivityCompat.checkSelfPermission(
            ApplicationContext.instance,
            Manifest.permission.READ_PHONE_STATE
        ) == PackageManager.PERMISSION_GRANTED
        ) {
            val telephonyManager =
                ApplicationContext.instance.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
            NewRelic.setAttribute("IMEI", telephonyManager.imei)
        }

ApplicationContextは、上記のQiita記事を参考に自分で作ったクラスです

IMEI取るためだけにここまでやるのは面倒かもしれませんが、意外と便利なケースがあるので、後のことを考えておくならやっておくのもありかなと思います

Context周りは正直まで全然理解できていないので、頑張って理解していきたいと思います

ではまた~

【Android】【Kotlin】retrofit2のつまずいたところ

Androidアプリ開発でretrofit使ってたんですが、そこで躓いた点を備忘録としてまとめておきます

クエリ部分に@Pathを使ってデータを渡せない

そのままですが、例えば下記のように@Queryでクエリパラメータを渡すことがあると思います

@GET("v1/get")
    fun searchBook(
        @Query("isbn") isbn: String
    ): Single<JsonArray>

この場合最終的なURLは {BASE_URL}/v1/get?isbn={isbn} となります これはなんの問題もないです

ただ以下のように書くと怒られます

@GET("v1/get?isbn={isbn}")
    fun searchBook(
        @Path("isbn") isbn: String
    ): Single<JsonArray>

何故かクエリを@Pathで渡そうとするとエラーになります できてもいい気がするんですけどね。なんでなんでしょうか?

ちなみにこんなエラーが出ます

must not have replace block. For dynamic query parameters use @Query.

@Urlオプション使うときはパラメータ設定ができない

上記のパラメータ設定を抽象クラスにして、それの具象クラスとして下記のような実装をしていました

class BookApiService @Inject constructor(){
    fun <S> create(serviceClass: Class<S>): S{
        val gson = GsonBuilder()
            .create()

        val retrofit = Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create(gson))
            .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
            .baseUrl("https://api.openbd.jp/")
            .client(httpBuilder.build())
            .build()

        return retrofit.create(serviceClass)
    }

    private val httpBuilder: OkHttpClient.Builder
        get() = OkHttpClient.Builder()
            .readTimeout(30, TimeUnit.SECONDS)
}

この場合、baseurlだけが違う似たようなクラスを何個も作らないといけなくなり、あまりかっこよくないな~と思って調べたところ、@Urlオプションを見つけました こんな感じで使えます

@GET
    fun searchBook(
        @Url url: String
    ): Single<JsonArray>

そして呼び出し時に引数としてurlを入れておくとbaseurlを上書きしてくれるようです

これしか勝たん、と思って下記のように使ったら怒られました

@GET("v1/get")
    fun searchBook(
        @Url url: String,
        @Query("isbn") isbn: String
    ): Single<JsonArray>

GETパラメータと@Urlは併用できないというエラーが出ます ちなみにPOSTでも同じです

エラーはこんな感じ

@Url cannot be used with @GET URL (parameter #1)

baseurlは「/(スラッシュ」)」で終わらないといけない

もうオチが見えた方もいらっしゃるかと思いますが、次にこうしてみました

class BookApiService @Inject constructor(){
    fun <S> create(serviceClass: Class<S>): S{
        val gson = GsonBuilder()
            .create()

        val retrofit = Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create(gson))
            .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
            .baseUrl("https://api.openbd.jp/v1/get")
            .client(httpBuilder.build())
            .build()

        return retrofit.create(serviceClass)
    }

    private val httpBuilder: OkHttpClient.Builder
        get() = OkHttpClient.Builder()
            .readTimeout(30, TimeUnit.SECONDS)
}
@GET
    fun searchBook(
        @Url url: String,
        @Query("isbn") isbn: String
    ): Single<JsonArray>

こうすると次は、ベースURLは/(スラッシュ)で終わらないとだめと怒られてしまいました エラーは下記の感じです

baseUrl must end in /

結局あきらめて具象クラスを複数作りましたとさ

いい解決策お持ちの方教えて下さいm( )m

まとめ

まだretorofit絶賛実装中なので随時更新していこうかなと思います

個人的にretrofitの英語はかなり易しいので好感が持てます エラーのときはイラっとするけど

【読書感想文】「マイクロサービスアーキテクチャ」

第二回目も読書感想文

今回はみんなお世話になっているオライリー社の「マイクロサービスアーキテクチャ」です

きっかけは当プロジェクトのマイクロサービスサービス化検討の際の勉強です

この本読んだからできるようになるわけねーだろっていうツッコミは勘弁してください

感想・まとめ

はじめに

  • マイクロサービスを取り巻く技術の進歩は早く、マイクロサービスを実現する特定の技術を習得することよりも、その本質的な考え方を理解することが重要である。

マイクロサービスとは

  • SOA(サービス指向アーキテクチャ)のひとつの実現形態であり、DDD(ドメイン駆動設計)、CI・CD(継続的インテーグレーション・デリバリ)、インフラ仮想化、自動化、アジャイル開発プロセス、といったさまざまな分野の技術や方法論を組み合わせることで成り立っている
    • これらが総合的に実践できなければその価値を享受できない。
  • DDDの「境界付けられたコンテキスト」が、ひとつのマイクロサービスの単位になる。
  • 外部から利用されるためのAPIを公開する。
    • その公開APIの呼び出し元(=コンシューマ)に影響を与えることなく単独で変更・本番リリース可能である。
  • あるマイクロサービスが停止しても、他のマイクロサービスは停止せずにサービスを提供し続け、システム全体としては、一部機能を切り離して稼働し続けることができる。
    • 停止した原因をきちんと探る必要がある
  • 必要なサービスだけを、必要なときにスケールさせることができる。
  • 自律的な生産性の高いチームによって開発され、所有・維持される。採用する技術や方法論は所有チームが自由に決定する。
    • 生産性の高いチーム=小規模コードベースで作業する小規模チーム
  • 簡単に捨てたり、作り直したりできる。(2週間で作り直せる程度の大きさ)

共有ライブラリやモジュールは?

  • チームやサービス間で機能を共有することができる
  • 技術多様性が失われる
    • 言語やプラットフォームが同じである必要がある
  • 独立してスケールすることが難しい
    • DLLを使っていない限り全体をデプロイする必要がある

マイクロサービスは銀の弾丸でもなければ、金のハンマーとして使いべきではない

マイクロサービスアーキテクトの振る舞い

  • 個々のマイクロサービスの内部詳細より、各マイクロサービス同士がどう連携するのか、それらのマイクロサービス全体の状態をどう監視するのか、と言ったシステム全体に注力すべきである。
    • 内部詳細は、所有チームに任せれば良い
  • マイクロサービスアーキテクチャでは、予測が困難な状況の下で多くのトレードオフな判断が必要になる。
    • 目標とそれを実現するための原則とプラクティスを定めることで対処できる。
      • 戦略的目標・・・会社や部門レベルで定める、儲けるためには何がどうあるべきか、と言う目標。
      • 原則・・・戦略的目標を達成するために、関係者が守るべき規則。
      • ラクティス・・・原則を守るための具体的な手段、技術的な標準ルールなど。
    • 小規模なチームだと、原則とプラクティスが結合される場合もある
  • 各マイクロサービスで共通の標準として、最低限以下のようなことを定めておく。
    • 各マイクロサービスと、システム全体の状態を監視するための仕組みやルール。
    • マイクロサービスがAPIを公開するための標準技術やルール。
      • 使用する定義済みインターフェース
        • 使いすぎない
    • あるマイクロサービスの障害がシステム全体に波及しないようにするための仕組みやルール。
      • エラーコードなど
  • 方針に従っているかを確認する手間を省くために下記を使用する
    • 手本(サンプルコード)
    • サービステンプレート
    • ※開発者に押し付け、やる気を削がしてはいけない
      • ユーザだけでなく、開発者たちにも快適に開発できる空間を提供する必要がある
  • 近道(方針に外れること)を行ってしまうと将来的な技術的負債になってしまうことを忘れない
  • 意思決定する際の注意点
    • 技術のリスクはグループで背負う
    • 同意できないことがあってもチームの決定には従ったほうが良い
      • チームが気づいておらず、確実に自分が正しいときは例外
  • システムが方針から逸脱した際はきちんとログに残す
    • それをもとに原則とプラクティスを変えていく

サービスのモデル化方法概要

  • 良いマイクロサービスを設計するには下記の2点がポイント
    • 他のサービスに影響することなく独立してデプロイできる疎結合
    • ドメインの境界づけられたコンテキストでまとめられた高凝集性
  • 経験のない新しいドメインを扱う場合、最初からコンテキスト境界を見極めてサービスの単位を決めるのは難しい
    • まずはモノリシックに作って、ドメインの理解が進むにつれて徐々にマイクロサービスに分離していくほうが安全。
    • まずは大まかな境界から分けていく、どんどん細かく分けていく。
      • 細かさの粒度はシステムによって異なる
  • コンテキスト境界を分離するときは、データの観点ではなく、そのコンテキストが外部に公開する振る舞いに注目したほうが良い。

結合(最重要パート)

  • マイクロサービスにおいては、各サービスをどのように結合するか?が最も重要な技術要素となる。
  • 可能な限り、サービスの呼び出し元に影響せずに変更・デプロイできるような結合方式を模索する。
  • サービスが外部に公開するAPIは、特定の技術に依存しないようにすることで、呼び出し元の技術選択の自由度を確保するとともに、進歩する技術を随時導入できるようにする。
  • データベース(および、その他のデータストア)の共有によるサービスの連携は、簡単ではあるが疎結合性と高凝集性の両方を失うことになり、マイクロサービスを採用する意味がなくなるので避けるべき。
  • 破壊的変更を回避するために、内部実装の詳細を隠す

サービス間の連携方法

  • サービス間の通信方法として、同期か非同期がある。

Untitled

  • あるサービスとその下流となる複数のサービスによってひとつの処理が構成される場合の制御方法として、オーケストレーションかコレオグラフィがある。

Untitled

  • コレオグラフィによる連携では、全てのサービスが処理を完了したのか、どこまで処理が進んでいるのか、と言ったイベントの処理状況を監視・追跡するための仕組みづくりが必要になる。
    • ビジネスプロセスがシステムには暗黙にしか反映されないため

リクエスト/レスポンス

  • サービス間の通信プロトコルとしては、HTTP(S)+RESTがデフォルトの選択肢となる。
    • RPCは運用の制約、呼び出し、脆弱性などの観点からおすすめはしない
    • ただ、HTTPは低遅延には不向きである
  • さらにHATEOASを採用することで、サービスの利用者は個々のAPIエンドポイントを把握する必要がなくなる。
    • サービスが公開するAPIエンドポイントを柔軟に変更できるメリットがある。(が、まだ積極的な採用事例は少ない)
    • 呼び出しが多くなるため、まずはクライアントサイドで実行し、後から移動するほうが良い

データフォーマット

  • JSONが多数派。
  • XMLもおすすめである
    • サポートツールが優れているため
  • フレームワークの選定には注意
    • ひどい密結合を強いられる場合がある
    • すべてのHTTPが対応しているとも限らない(PUT、DELETEなど)

イベントベース

  • イベントベースのサービス間通信を実現するために、あるサービスがイベントを発行(パブリッシュ)し、他のサービスがそのイベントを受信(サブスクライブ)するための基盤の構築が必要になる。
  • 受信したイベントの処理に失敗した場合を考慮し、その仕組みを構築する必要がある。
    • 失敗したイベントの処理のリトライ。何回リトライするか、どれくらいのインターバルを置くか。
    • リトライしても最後まで成功しなかったイベントの扱い。エラーキューに移動させて、後で別途リカバリできるようにするなど。
    • エラーキュー内のイベントの監視/参照とリカバリ。個々のマイクロサービス独自ではなく、標準的な仕組みがほしい。
  • 複数のサービス、イベントキューにまたがって処理が連動していくので、それらを紐付けて追跡できる仕組みがないと運用できない。一連のイベントの最初にユニークな相関IDを発番し、それをイベントに乗せて伝播させるようにする。

コードの統合

  • ロギングなどの汎用的なものを除き、マイクロサービス(コンテキスト境界)をまたいでコードを共有しないほうが、個々のサービスの独立性を維持できる。
  • サービスが公開するAPIを利用するためのクライアントライブラリ(SDK)を提供する場合は、コンテキストのロジックがクライアントライブラリに漏れないように注意する。また、クライアントライブラリのバージョンアップは利用者が任意のタイミングで行えるようにする。
    • セマンティックバージョン表記(ex.1.0.2)を使用するとわかりやすい
    • URIにバージョン情報を入れるかどうかは好みである。どちらも利点がある
  • 公開したサービスの仕様を変更する場合は、可能な限り下位互換性を保ち、呼び出し元への破壊的変更を回避する。
  • 同じサービスに異なるエンドポイントを作成する
  • 同じサービスで複数のバージョンを実行して古いエンドポイントに対応する
    • こちらの場合、サービスの内部バグを修正する必要がある
    • 2つの異なるサービスを修正してデプロイする必要がある
    • サービスのコードベースを分岐する必要があり、必ず問題となる
    • ブルーグリーンデプロイやカナリヤリリースのように、一時的に複数のバージョンが存在するのは問題ない

ユーザーインターフェースとの統合

  • ユーザインターフェースを合成レイヤと考える
  • 複数のマイクロサービスの結果を1つの画面に表示させる場合、いくつかの方法がある。
  • API合成
    • XMLJSONデータを返す複数のサービスを画面から呼び出し、フロントエンドで画面を生成する。
    • バイスごとにレスポンスを変更するのが難しい
    • UI担当とサービス担当が違う場合、うまく表示されなかったり、UIが最適化しない恐れがある
  • UI部品合成
    • サービスがUI部品を生成して返し、フロントエンドでそれらを組み合わせて画面を構成する。
    • 各部品とチームの担当が一致する場合は良い
      • 同じチームがUIもサービスも担当できるので、迅速に公開できる
      • 各チームでUIの一貫性を保証する必要がある(CSS、HTMLComponent)
  • BFF(Backend For Frontend:フロントエンド向けのバックエンド)
    • サーバー側に、複数のサービス呼び出しを画面向けにまとめるゲートウェイ層を設ける。
      • 1つモノリシックなゲートウェイにしてしまうと、システムが大きくなったときに様々なチームの変更が入り、機能変更のたびに修正する必要になってしまう
    • 特定のUXの提供に特化した処理のみを追加する
      • ロジックはサービスの中のみに記述する
  • 上記手法を組み合わせて行う場合もある

レガシーシステムとの統合

  • 既存システムやプロダクトと統合する場合は、その既存システムへの呼び出しをマイクロサービスでラップし、徐々に既存システムを新しいサービスに置き換えていく(ストラングラーパターン)ことでビッグバンな変更を避けてマイクロサービスを導入できる。

モノリスの分割

  • モノリシックなシステムをマイクロサービスに分割する際は、境界づけられたコンテキスト で分割する。
  • 一気にマイクロサービス化するのではなく、影響の小さい機能から徐々にマイクロサービスに分割していく。こうすることでチームがマイクロサービスを学習する機会を設け、導入のリスクを減らす。

データベースの分割

  • DBは依存関係の原因となるケースが多い
  • 境界づけられたコンテキストをまたぐテーブル共有はせず、サービスを介してデータをやり取りする。
    • ただし、これによって外部キーによるデータの関連やトランザクション整合性を保証できなくなる(=結果整合性になる)。
  • データ(国コードなどのメタデータなど)もコンテキストをまたいで共有することはできない。
    • 解決策
    • 各サービスで同じデータを重複して持つ
      • 一貫性の課題を招く可能性がある
    • 列挙型などのコード化して各サービスに取り込む
      • 一貫性の課題は残るが、DBを変更するのは簡単
    • 共有データを扱う独立したサービスにするなど。
      • データ量や複雑な場合は良い手法(国コードレベルだとやりすぎ)
  • 共有データは新パッケージを作成し、それを重複しているパッケージに公開
  • 共有テーブルは分割する
    • データベースの分割は段階的に。まずサービスはモノリシックのままでコンテキストごとにデータベースを分割し、次にサービスを分割する
  • データベースを分割することでデータが結果整合性となる場合は、その整合性が取れていない状態を表現するドメインの概念(例 処理中の注文など)を導入する。

帳票の分割

  • 一般的に帳票には複数のコンテキストの情報が集約される。サービスを分割することで帳票のために情報を集約する方法を検討する必要がある
    • モノリシックサービスでは、全てのデータをまとめたデータベースからリードレプリカを作成し、そこに対してレポートの作成を行っているケースが多い -分割法
  • 各サービスを呼び出して帳票で必要な情報を収集する
    • 必要に応じてサービスに情報をバッチ的にまとめて取得するAPIを設ける。
    • あまりおすすめはしない
  • 帳票用のデータベースを設けてサービスのデータを連携する(データポンプ)
    • イベントベースでサービスからの変更イベントをサブスクライブして帳票用データベースにデータ投入することを検討する。
    • どのイベントが処理済みかを格納しておくと、差分のみを取得して実行することができる
      • イベントストリームのタイムスタンプを活用する
    • イベントデータポンプはサービス内部とあまり結合しないため、独自の進化が可能
    • 必要なすべての情報をイベントとしてブロードキャストしないといけない欠点がある
    • Netflixはバックアップするデータのコピーを作成して、安全な場所に格納する手法(バックアップデータポンプ)を採用している

まずはモノリスになった根本原因を理解することが大切

デプロイ

モジュール

  • マイクロサービスのCIでは、個々のサービスを独立してビルド・デプロイできるよう、サービスごとにコードリポジトリやビルドジョブを構成するのが良い。
    • 1つのソースコードレポジトリからサービスごとにビルドを行うほうが、全てをビルドするよりはマシだが、おすすめはしない
  • ビルドのサイクルを最適化するために、ビルドにステージを設けてビルドパイプラインを構成する。ステージの最初のほうでは高速に実行できるテストを実施してフィードバックループが早く細かく回るようにし、ステージが進むにつれて低速で総合的なテストを実施するようにする。
    • サービスごとに独立したビルドパイプラインを構成し、ビルドステージの進行を管理できるCI・CDツールの選定が望ましい。
    • 手動で実施する受入れテストもステージのひとつとして管理するべき。
    • 新たにプロジジェクトを作る場合、最初は大きめのサービスにしておくほうが良い例外もある
      • サービスにまたがる変更のリスクを軽減できる
      • サービスAPIが安定したらそれぞれのビルドに移行する

インフラ

  • モジュールが稼働するために必要なインフラやミドルウェアも自動的に構成できるようにする必要がある。
    • モジュールを実行環境のプラットフォーム(OS)ネイティブのパッケージ管理ツールにあわせる。
    • モジュールとインフラ、ミドルウェアをひとまとめにしたVMイメージを作る。
    • イメージの構築に長い時間がかかるのが欠点
    • イメージを構築するために必要なツールがプラットフォームごとに異なる
      • Packer でコード化すると便利
  • CI・CDによって構成され稼働しているサーバは、原則として手作業で変更(SSH接続したり、管理コンソールを使ったり)してはいけない。サーバの構成を変更するには、ビルドパイプラインを使ってビルド・デプロイを行う。

環境設定

  • サービスが必要とする設定項目は、DB接続情報のような環境に依存する情報だけとし、環境ごとに変わる項目は必要最小限に留めるようにする。
  • 1つのモジュールに対して構成を別で管理する
    • 環境ごとにモジュールを作成するのはNG
    • 環境依存の情報はモジュールには含めずに設定ファイルなどで別管理できるようにする。
      • プロパティファイルやパラメータなど

サービスからサーバへのマッピング

  • 1台のサーバに1つのマイクロサービスをデプロイするべきである
    • 1台のサーバ(およびアプリケーションサーバのようなコンテナ)に複数のサービスを同居させるべきではない。
      • 各サービスを独立してデプロイし、運用状況を監視することが困難。
      • 共有されるサーバがボトルネックとなって、サービスを開発するチームの自律性を妨げる。
      • サーバのスケーリングが困難。
      • サーバを共有するのは、サーバリソースの最適化(節約)が目的であり、仮想化技術などを使えばその必要はなくなる。
      • 技術選択が制限され、イメージベースやイミュターブルサーバなどが使えない
  • 仮想環境を構築する際はDockerがおすすめ
    • その他候補
    • Vagrant
      • ノートPC上に仮想クラウドを提供するデプロイプラットフォーム
      • 主に開発環境やテスト環境用で使用
      • 高速なターンアラウンドが実現可能
      • 開発マシンに大きな負荷がかかり、システム全体を立ち上げるために一部の依存関係をスタブ化しないといけない場合がある
    • Linuxコンテナ
      • ハイパーバイザーなしで動作するため、リソースの節約やフィードバックの面で利点がある(高速で起動可能)
      • ハイパーバイザーが行っていたルーティングを自分で設定する必要がある

デプロイツール

  • モジュールのデプロイは、以下の情報をパラメタとして受け取るコマンドラインツールで実行できるが望ましい。
    1. モジュール
      • デプロイするモジュールの名前や場所。
    2. バージョン
      • デプロイするモジュールのバージョン。
    3. 環境
      • デプロイ先の環境。
  • これを手元のターミナルやCI・CDツールから実行する。

マイクロサービスの概要をさらうという意味ではいいとおったけど、悪く言えばその程度ですね

「ああ!なるほど!」と言った新しい発見はなかったイメージ

ただこれは私の実力や経験が追いついていない節はある(ピンときていないだけ)ので、3年後くらいに読んでみようかなと思います

その頃にはマイクロサービスという言葉も死語になってたりしてw(少しあり得そうで笑えない)