モノリスからマイクロサービスへ: マイクロサービスを選択する理由

モノリスからマイクロサービスへ』を読んだ

マイクロサービスを選択する上での参考になりそうな情報があったのでメモ マイクロサービスを導入するためには明確な理由を持つべきであり、同じ結果を達成できる他の方法も踏まえて検討すべし、とした上で、2.2章でマイクロサービスを選択する代表的な理由と、同じ結果を達成できる他の方法が列挙されている

  1. チームの自律性を高めるため
    マイクロサービスによって、チームの規模を小さく保つ
    代替案 チーム設計が本質で、マイクロサービス化は必須ではない モジュラーモノリスでも実現できる

  2. 市場投入までの時間を減らす
    デプロイ独立性から、各マイクロサービスは他サービスとの調整なくリリースできる
    代替案 リリースを早めるための手法をいくらでもある ボトルネックに効く対策が必要

  3. 負荷への費用対効果が高いスケーリング
    マイクロサービス単位でのスケールができる
    代替案 既存のモノリスのスケールアウト、スケールアップで十分な場合も多い

  4. 堅牢性を改善する
    サービス全体を止めなくても、マイクロサービスを変更できる、という意味合い
    単にマイクロサービスは堅牢性があるというわけではなく、 "むしろ、マイクロサービスはネットワークの分割やサービス停止などに一層耐えられるようなシステムの設計を必要とする"
    代替案 LBによる冗長化や、地理的なインフラの分散などで十分な場合も

  5. 開発者の数を増やす
    マイクロサービスごとにチームができるので開発者のスケールができる
    代替案 モジュラーモノリスでも複数チームで担当を分けることができる
    ただしモジュラーモノリスではデプロイ時はチーム間で調整が必要なため限界がある

  6. 新しい技術を受け入れる
    サービスごとに別の技術を選択できるので、一部のサービスだけ新しい技術を採用する、といったことができる

React の context API があれば redux は不要なのか

React hooksの登場とともに useContextが現れた当時、reduxは必要なくなる?と疑問に思って軽く調べた記憶がある。 当時は、mediumの記事 にたどり着いて
hooksはreduxに取って代わるものではなく、必要であるのであれば変わらずredux導入しなさい、という結論だけそうなのかーと受け入れていたが、 理解が浅く具体どんな場合にreduxが適するのか、が分かっていなかった。
本日改めて記事を読み返してちょっと腹落ちしたのでメモしておく。

本題

useContextとreduxを比べるとき、解決したいこととしては以下だと思う。
- 複数のコンポーネントで同一のstateを利用したい
- だが、親コンポーネントからpropを何度も経由してstateを受け渡すのは辛い

https://zenn.dev/luvmini511/articles/61e8e54853bc13#1.-%E3%83%A1%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%AC%E3%83%BC%E3%82%B8%E3%81%AE%E5%BF%85%E8%A6%81%E6%80%A7

上記の記事の 「1.メインストレージの必要性」の話。

これに対するuseContextとreduxが提供する機能にはそもそも違いがある。

mediumの記事には以下のようにあり

And while some developers opt to use context to manage their entire application state, that’s not really what it’s designed for. According to the docs,

Context is designed to share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme, or preferred language.

In other words, things that aren’t expected to updated frequently.

developers は contextをアプリケーション全体のstate管理に利用しようとするが、contextはそういった目的で作られた訳ではない。
"contextは、認証されているユーザー、テーマ、優先言語など、Reactコンポーネントのツリーの「グローバル」と見なすことができるデータを共有するように設計されています。"
言い換えると、contextは頻繁に更新されることが期待されていないものである。

ということだった。
つまり、頻繁に更新されるstateを複数コンポーネントで利用するなら、contextよりもreduxの方がより目的に適した手法である。
contextを利用しても実現できるが、以下のようなデメリットもある。
コンポーネントの再利用性が損なわれる
↑ contextのProvider配下のコンポーネントは、context経由でstateを受け取れることを前提に作られ、 contextのProvider配下以外には利用できなくなるため 。 reduxであれば、一元管理のstoreにはどこからでもアクセスできるため、再利用しやすい 。
・contextのProvider配下のコンポーネントの不要な再レンダリングを起こしやすい
↑ 基本的にはcontextで管理しているstateが更新されると、contextのProvider配下のコンポーネントは全て再レンダリングされるため(回避策はあるが)。
reduxであれば、connectされている(またはuseSelectorを利用している)コンポーネントのみが再レンダリングされる 。

また、reduxを使うメリットとしては、複数のコンポーネントでstateを利用できるだけではなく以下のようなものがあり、これもreduxを利用する大きな理由となる。
- middlewareを利用できる
- 開発者ツールやデバッグ機能が充実している

ただし、reduxは学習コストが高い上にコード記述量も増えるので、導入は慎重に行いたいというのが自分の考え。

まとめ

reduxを利用するモチベーションは以下
- 頻繁に更新されるstateを複数コンポーネントで共有したい
- stateの更新に際してmiddlewareを利用したい
- stateの更新に際してreduxの充実した開発者ツールやデバッグ機能を利用したい

これらが、redux導入によるデメリット (学習コスト、コード記述量の増加、バンドルサイズの増加、ライブラリ管理コストなど) に見合っていれば、導入を検討したい。

新規VPC内のEC2をインターネットに繋ぐ

これまでデフォルトVPCを利用したり、既にインフラエンジニアが作成してくれたVPCを利用してきており、 新しくVPC作ってインターネットに繋がるように設定したことがなかったので、自分で手を動かしてやっておく。

今まではこのようなチュートリアル的な内容はやるだけやって満足していたが、世に似たような記事が溢れているので記事を書くことをためらっていたが、 やるだけだと数ヶ月離れると全て忘れてしまうので、自分のために記事に起こして、思い出し工数を少しでも下げてみようという試みから残しておくことにする。

デフォルトVPCを使わず、新規VPCからインターネットに繋がるEC2を立てる手順

ちなみにデフォルトVPCは、AWSアカウントに付いてくるいい感じの設定のVPCであり、新しくVPCをデフォルト設定のまま作成したものとは別物。

VPCおよびサブネット作成.
② インターネットゲートウェイ(IG)作成.
③ 作成したIGをサブネットに紐付け.
④ IGをサブネットに紐づけるだけではインターネットに繋がらず、以下のどちらかでルートテーブルに追加する必要がある.
 ④ -A 作成したVPCにデフォルトで付いてくるメインルートテーブル の設定を編集して作成したインターネットゲートウェイ用のレコードを追加する.
 ④ -B ルートテーブルを新規で作成して、 作成したVPCに紐づけ & ルートテーブルの設定を編集してレコード追加.
⑤ EC2インスタンスを作成したVPC上、 自動割り当てパブリック IP: 有効 として作成.

作成したEC2にsshできればOK。EC2の中からcurlgoogleとかを叩けば繋がることも確認できる。

メインルートテーブル

VPCを作成すると自動的に作成されるもので、明示的にルートテーブルに紐づけていないサブネットは、このメインルートテーブルに自動的に紐づけられる。

JavaScript の Promise について

しばらく触れていないとすぐ忘れてしまうので Promise についてのメモを残しておく。

基本的には JavaScript Primer を読み返すと理解が早い。

Promise の状態

Promiseの状態
Promiseの状態

new Promise(executer) すると Pending 状態の Promise インスタンスが生成される。 executer は同期的に(つまり、new Promise を呼び出した瞬間に)実行される。 executer はお馴染みの 以下のような関数。

(resolve, reject) => {
    // 処理が成功したときはresolveを呼ぶ
    // 処理が失敗したときはrejectを呼ぶ
}

この関数内で同期的な関数しか呼んでいなければ、すぐさま Fulfilled or Rejected の状態に遷移するし、 非同期の関数を呼んでいれば非同期処理の結果で Fulfilled or Rejected の状態に遷移する。

Promise.resolve と Promise.reject

Promise.resolve は Fulfilled 状態の Promise インスタンスを返す。 実態は new Promiseの糖衣構文。

// const fulfilledPromise = Promise.resolve(); と同じ意味
const fulfilledPromise = new Promise((resolve) => {
    resolve();
});

Promise.reject も Rejected 状態の Promise インスタンスを返すだけで同様。

例外

new Promise(executer) のexecuter 内で発生した例外は、 reject と同じ扱いになる。 executer 内の

reject(new Error(message));

throw new Error(message);

はほぼ同じ。

then と catch

Promise インスタンスに対して then(onFulfilled, onRejected) を呼び出すと、Promise インスタンスの状態が Fulfilled or Rejected となった時に onFulfilled or onRejected が呼び出される。 catch は then(undefined, onRejected) と同じ処理を catch(onRejected) として書ける。

thenの返り値

then は Promise インスタンスを返す。 これによって、Promiseチェーンが実現されている。

then(onFulfilled, onRejected) において、

  • onFulfilled or onRejected が Promiseインスタンスを返す場合
    → この場合特別扱いされ、onFulfilled or onRejected が返した Promise インスタンスをthen もそのまま返す。
  • onFulfilled or onRejected が Promiseインスタンス以外の値を返す場合
    → onFulfilled or onRejected が返したvalueに対して then は Promise.resolve(value)を返す。
  • onFulfilled or onRejected 内で例外が発生した場合
    → 発生したerror に対して then は Promise.reject(error) を返す。

Go の Must のついたメソッド

今日は Go について書きます。今回はさらっと。

github.com/jmoiron/sqlx を使っていて MustExec と Exec の違いなんだっけ、となったのでどんなメソッドに Must が付くか調べ直しました。

結論

先に結論です。

Must のついたメソッド: エラーが発生した場合に panic を発生させる関数

Must のないメソッド: エラーが発生した場合、返り値で error を返す関数

よって、絶対にエラーが発生しない(正規表現コンパイルとか、html の template とかコンパイル時には既に確定している内容)処理の場合のみ Must のついたメソッドを使用してよいです。

実行時に panic を起こす可能性がある場合は Must のないメソッドを使用し、エラー処理をしっかり行う必要があります。

sqlx の例

まずは冒頭で触れた sqlx の godoc を見てみます。

func (*NamedStmt) Exec

func (n *NamedStmt) Exec(arg interface{}) (sql.Result, error)

Exec executes a named statement using the struct passed. Any named placeholder parameters are replaced with fields from arg.

func (*NamedStmt) MustExec

func (n *NamedStmt) MustExec(arg interface{}) sql.Result

MustExec execs a NamedStmt, panicing on error Any named placeholder parameters are replaced with fields from arg.

Exec は 返り値として sql.Result と error を返します。 MustExec は返り値として sql.Result のみを返し、

panicing on error

します。 その他は基本同じですね。

プログラミング言語 Go での記述

プログラミング言語 Go を読んだときに何回か出てきたなーという印象だったため、読み返して見ました。

4.6 節で html/template パッケージの例を出している場所で以下のように述べられています。

テンプレートはたいていコンパイル時に確定しているので、テンプレートのパースに失敗するというのはプログラムの致命的なバグを示しています。template.Must ヘルパー関数はエラー処理をより便利にしています。その関数はテンプレートとエラーを受け付けて、エラーが nil であるかを検査し(nil でなければパニックになります)、そしてそのテンプレートを返ます。

事前に用意した正しいテンプレートを用いる場合、パースに失敗するはずがない(あれば致命的なバグ)であるということですね。 逆に、(あまりないでしょうが)プログラム中で動的にテンプレートを生成する場合は、パースに失敗しうるのでエラーチェックを正しく実装すべきです。

regexp

Must が出てくる他の例は標準パッケージの regexp です。

正規表現も固定の文字列を用いる場合がほとんどで、コンパイル時には確定しているというわけですね。

godoc には以下のようにあります。

func MustCompile

func MustCompile(str string) *Regexp

MustCompile is like Compile but panics if the expression cannot be parsed. It simplifies safe initialization of global variables holding compiled regular expressions.

終わりに

他の言語ではあまり見ないような気がするので、Go 特有の命名でしょうか。 適切に使い分けていきたいと思います。

Javaのアクセスレベル (public, protected, パッケージプライベート, private)の使い分け

前回Javaの4つのアクセスレベルについて書きました。

今回はその4つのアクセスレベルをどう使い分ければ良いか書きたいと思います。

結論

要件を満たす中で、最小のアクセスレベルを選択する。

トップレベルのクラス

他のパッケージに公開する必要がある
→ public

他のパッケージに公開する必要がない

→ パッケージプライベート

クラスメンバ

他のパッケージに公開する必要がある

→ public, protected

他のパッケージに公開する必要がない

→ パッケージプライベート, private

基本方針

Effective Java には以下のように書かれています。

大まかなやり方は単純です。すなわち、各クラスやメンバーをできる限りアクセスできないようにすべきです。

基本的にはこの方針に従っていけば問題ありません。

各アクセスレベルを理解しておき、今実装しているクラスやメンバをどこまで公開する必要があるかを考えていきます。

public, protected vs パッケージプライベート, private

「public, protected」と「パッケージプライベート, private」の間にまず大きな違いがあります。

public, protected は異なるパッケージからアクセス可能、つまりAPIを公開することになります。

なので、トップレベルのクラス・インターフェースに関しては他のパッケージに公開する必要がある場合のみ public にし、必要がなければパッケージプライベートにすべきです。

メンバーに関しても、他のパッケージに公開する必要がある場合のみ public にします。

public vs protected

ここでも できる限りアクセスできないようにすべき という方針に従うのみです。

protected を使うと、別パッケージではそのクラスを継承したクラス内でしか見れなくなります。

そのため、継承せずに別パッケージから見る必要があれば public 、そうでなければprotected とします。

しかし、 protected を使う場合はパッケージプライベートや private ではダメなのか再考の余地があります。

先に述べた通り public や protected は API を公開することになるため、安易に実装を変えられなくなりますので、 protected を使わずともパッケージプライベートで要件を満たせるのであれば大きなリターンがあります。

Effective Java でも以下のように書かれています。

protected のメンバーの必要性は、比較的にまれであるべきです。

継承させて使うことを意図したクラスを公開するというのは決して多くないと思います。

パッケージプライベートvs private

この2つは基本的にクラスの公開APIに影響しません。

同一パッケージからそのクラスやクラスのメンバにアクセスする必要があればパッケージプライベートを使います。

一番分かりやすいのは、テストコードを書く時でしょう。

パッケージプライベートであれば、同一パッケージのtestからメソッドを呼び出すことができます。

その必要がなければprivateを使いましょう。

クラスの public の API を注意深く設計した後に、無意識に行うべきことは、他のすべてのメンバーを private にすることです。

まとめ

何も考えずに public にしてしまうのは楽ですが、その分大きな責任が伴います。

基本的には public なクラスのメンバは全て private にしてみて必要なら広げてみるというやり方を取れば、都度アクセスレベルを意識できそうですね。

Javaのアクセスレベル (public, protected, パッケージプライベート, private)

ブログを解説してから1ヶ月以上放置してしまいましたが、ようやく初記事です。 ちゃんと定期的に技術ブログできる人、尊敬です。

今回は Effective Javaを片手にJavaのアクセスレベルについて書こうと思います。

Effective Java は1年ほど前に会社の研修で読んだもので、研修中は訳者の柴田芳樹さんに講師として直々に解説をいただきました。

アクセスレベルについては 第4章 クラスとインターフェースの項目15, 16で触れられています。

4つのアクセスレベル

まずは、トップレベルの(ネストしていない)クラスとインターフェースに関するアクセスレベルと、メンバー(フィールド、メソッド、ネストしたクラス、ネストしたインターフェース)に関するアクセスレベルを見ていきます。

トップレベルのクラス、インターフェース

public パッケージプライベート
同一パッケージ内
全ての場所 -

メンバー

public protected パッケージプライベート private
宣言されたトップレベルのクラス内
同一パッケージ内 -
宣言されたクラスのサブクラス内 - -
全ての場所 - - -

public, protected, privateの修飾子をつけるとそれぞれのアクセスレベルになります。

何も修飾子をつけないとパッケージプライベートになります。(ただし、インターフェースのメンバーはデフォルトでpublicです。)

具体例

実際に試してみましょう。

トップレベルのクラス

トップレベルのクラスからです。

package topclass.access1;

// publicなクラス
public class PublicClass {
    public String str = "Hello, world!";
}
package topclass.access1;

// パッケージプライベートなクラス
class PackagePrivateClass {
    public String str = "Hello, world!";
}

publicなクラスと、パッケージプライベートなクラスを作成しました。

続いて、別のパッケージからこれらのクラスをインスタンス化します。

package topclass.access2;

import topclass.access1.PublicClass;

public class AccessPublicClass {
    public static void main(String[] args) {
        PublicClass c = new PublicClass();
        System.out.println(c.str);
        // Hello, world!
    }
}

publicなクラスはアクセスできました。

package topclass.access2;

import topclass.access1.PackagePrivateClass;

public class AccessPackagePrivateClass {
    public static void main(String[] args) {
        PackagePrivateClass c = new PackagePrivateClass();
        // java: topclass.access1のtopclass.access1.PackagePrivateClassはpublicではありません。パッケージ外からはアクセスできません
    }
}

パッケージプライベートなクラスはコンパイルエラーになります。

アクセスするために、同じパッケージでインスタンス化してみましょう。

package topclass.access1;

public class AccessPackagePrivateClass {
    public static void main(String[] args) {
        PackagePrivateClass c = new PackagePrivateClass();
        System.out.println(c.str);
        // Hello, world!
    }
}

アクセスできました。

クラスメンバー

続いて、クラスのメンバーの場合です。

package member.access1;

public class PublicClass {
    public int publicValue = 1;
    protected int protectedValue = 2;
    int packagePrivateValue = 3;
    private int privateValue = 4;
}

それぞれのアクセスレベルをもつメンバ変数を用意しました。

まずは、同一のパッケージからアクセスします。

package member.access1;

public class AccessPublicClass {
    public static void main(String[] args) {
        PublicClass c = new PublicClass();
        System.out.println(c.publicValue); // 1
        System.out.println(c.protectedValue); // 2
        System.out.println(c.packagePrivateValue); // 3
        // System.out.println(c.privateValue);  コンパイルエラー
        // java: privateValueはmember.access1.PublicClassでprivateアクセスされます
    }
}

別のクラスからは、private変数にはアクセスできません。

次に、別のパッケージからアクセスしてみます。

package member.access2;

import member.access1.PublicClass;

public class AccessPublicClass extends PublicClass {
    public static void main(String[] args) {
        PublicClass c = new PublicClass();
        System.out.println(c.publicValue); // 1
        // System.out.println(c.protectedValue);   コンパイルエラー
        // java: protectedValueはmember.access1.PublicClassでprotectedアクセスされます
        // System.out.println(c.packagePrivateValue); コンパイルエラー
        // java: member.access1.PublicClassのpackagePrivateValueはpublicではありません。パッケージ外からはアクセスできません
    }
}

こうすると、public変数のみアクセス可能になります。

では、別パッケージからprotected変数にアクセスしてみます。

package member.access2;

import member.access1.PublicClass;

public class AccessPublicClass2 extends PublicClass {
    public static void main(String[] args) {
        AccessPublicClass2 c = new AccessPublicClass2();
        System.out.println(c.publicValue); // 1
        System.out.println(c.protectedValue); // 2

        PublicClass c2 = new AccessPublicClass2();
        // System.out.println(c2.protectedValue); コンパイルエラー
        // java: protectedValueはmember.access1.PublicClassでprotectedアクセスされます
    }
}

PublicClassを継承したサブクラスを作成し、そのインスタンスを通じてアクセスできます。

ただし、インスタンス変数の型を親クラスとしてしまうと、アクセスできません。

長くなってしまったので今回はここまでとし、次回にこれらのアクセスレベルの使い分けについて書きたいと思います。