マイクロフレームワークがモジュール、ミドルウェア、境界を明確にしたカスタムアーキテクチャをチームで組み上げる方法と、トレードオフ、パターン、落とし穴を学びましょう。

マイクロフレームワークは、リクエストを受け取り、適切なハンドラへルーティングし、レスポンスを返すという本質に焦点を当てた軽量なウェブフレームワークです。フルスタックフレームワークとは異なり、管理画面、ORM/データベース層、フォームビルダー、バックグラウンドジョブ、認証フローなどをすべて同梱することは通常ありません。代わりに小さく安定したコアを提供し、製品に本当に必要なものだけを追加できるようにします。
フルスタックフレームワークは家具付きの家を買うようなもので、一貫性と便利さがありますが、増改築が難しくなりがちです。マイクロフレームワークは構造的には健全な空の空間に近く、部屋や家具、ユーティリティを自分で決められます。
その自由こそがカスタムアーキテクチャという意味です—チームのニーズ、ドメイン、運用上の制約に合わせてシステム設計を形作ること。平たく言えば、コンポーネント(ロギング、データアクセス、バリデーション、認証、バックグラウンド処理)を自分で選び、それらの接続方法を決めるということです。"ワン・ライト・ウェイ"に従う必要はありません。
チームがマイクロフレームワークを選ぶ理由には次が含まれます:
ここでは、マイクロフレームワークがどのようにモジュール設計を支援するか、ビルディングブロックの合成、ミドルウェアの利用、依存性注入の導入方法を、プロジェクトを過度に複雑にしない形で説明します。
特定のフレームワークを行ごとに比較したり、マイクロフレームワークが常に優れていると主張したりはしません。目的は構造を意図的に選び、要件の変化に合わせて安全に進化させるための助けとなることです。
マイクロフレームワークは、アプリケーションをキットのように扱うと最も効果を発揮します。意見の強いスタックを受け入れるのではなく、小さなコアから始め、明確な理由があるときだけ機能を追加します。
実用的な「コア」は通常次の要素だけです:
これだけで動作するAPIエンドポイントやウェブページを出荷できます。その他は具体的な理由ができるまでオプションです。
認証、バリデーション、ロギングが必要になったら、別々のコンポーネントとして追加します—理想的には明確なインタフェースの背後に隠すべきです。これによりアーキテクチャが理解しやすくなります:新しい要素は「どの問題を解決するか?」と「どこに差し込まれるか?」に答えられるべきです。
「必要なものだけ追加する」モジュールの例:
初期段階では、あなたを閉じ込めない解決策を選びましょう。深いフレームワークの魔法より薄いラッパーや設定を好みます。モジュールを差し替えられるなら、ビジネスロジックを書き直す必要はありません。
アーキテクチャ選択の簡単な完了の定義:チームが各モジュールの目的を説明でき、1〜2日で差し替えられ、個別にテストできること。
マイクロフレームワークは設計上小規模であり、そのためアプリケーションの「臓器」を選べます。これがカスタムアーキテクチャを実用的にする理由です:最小から始め、実際に必要になったときだけ部品を追加できます。
多くのマイクロフレームワークベースのアプリは、URLをコントローラ(またはよりシンプルなリクエストハンドラ)にマッピングするルータから始まります。コントローラは機能別(billing、accounts)やインターフェース別(web vs API)に整理できます。
ミドルウェアは通常リクエスト/レスポンスの流れをラップし、横断的関心事の処理に最適です:
ミドルウェアは合成可能なので、グローバルに適用することも(全てにロギングが必要)、特定ルートだけに適用することもできます(管理系エンドポイントはより厳格な認証が必要)。
マイクロフレームワークはデータ層を強制しないため、チームと負荷に合ったものを選べます:
良いパターンはデータアクセスをリポジトリやサービス層の背後に隠すことです。後でツールを切り替えてもハンドラ全体に影響が波及しません。
すべての製品が初日から非同期処理を必要とするわけではありません。必要になったらジョブランナーとキュー(メール送信、映像処理、Webhook)を追加します。バックグラウンドジョブはドメインロジックへの別の「エントリポイント」として扱い、HTTP層と同じサービスを共有してルールを重複させないようにします。
ミドルウェアはマイクロフレームワークが最も力を発揮する場所です:すべてのリクエストに共通する処理を各ルートハンドラに増やさずに扱えます。目的は単純です:ハンドラはビジネスロジックに集中させ、配線や周辺処理はミドルウェアで行うこと。
同じチェックやヘッダを各エンドポイントで繰り返す代わりに1回だけミドルウェアに追加します。クリーンなハンドラは次のように見えます:入力をパースし、サービスを呼び出し、レスポンスを返す。認証、ロギング、バリデーションのデフォルト、レスポンス整形などは前後で行えます。
順序は振る舞いです。読みやすく一般的なシーケンスは:
圧縮が早すぎるとエラーを見逃す可能性があり、エラーハンドリングが遅すぎるとスタックトレースが漏れたり一貫しない形式を返したりするリスクがあります。
X-Request-Id ヘッダを追加し、ログに含める。{ error, message, requestId })にマップする。ミドルウェアを目的別にグループ化し(観測、セキュリティ、パース、レスポンス整形)、適切なスコープで適用します:本当に普遍的なルールはグローバルに、特定領域(例:/admin)にはルートグループミドルウェアを。各ミドルウェアには明確な名前を付け、セットアップ付近に順序の期待値を短くコメントで残しておくと将来の変更で振る舞いが壊れにくくなります。
マイクロフレームワークは薄い「リクエストIN、レスポンスOUT」のコアを与えます。その他すべて(データベース、キャッシュ、メール、サードパーティAPI)は差し替え可能にするべきです。ここで**Inversion of Control(IoC)とDependency Injection(DI)**が役立ちますが、コードベースを大掛かりな実験にしないように注意します。
機能がデータベースを必要とすると、その機能の中で直接インスタンスを作るのは誘惑です(“ここで new database client”)。しかしその場所はその特定のDBクライアントに強く結合してしまいます。
IoCは逆のやり方をします:機能は必要なものを要求し、アプリの配線がそれを渡すのです。これにより機能は再利用しやすく、変更も簡単になります。
依存性注入とは、内部で生成するのではなく外から依存を渡すことを指します。マイクロフレームワークのセットアップでは、起動時に行われることが多いです:
大きなDIコンテナは不要です。まずは簡単なルールを:依存は1か所で構築し、下方に渡す。
コンポーネントを差し替え可能にするには、「必要とするもの」を小さなインタフェースで定義し、特定ツールのアダプタを書くとよいです。
例:
UserRepository(インタフェース):findById, create, listPostgresUserRepository(アダプタ):Postgresを使った実装InMemoryUserRepository(アダプタ):テスト用実装ビジネスロジックは UserRepository のことだけを知っていて、Postgresのことは知りません。ストレージの切替は設定上の選択になり、書き換えは不要になります。
同じ考え方は外部APIにも適用できます:
PaymentsGateway(インタフェース)StripePaymentsGateway(アダプタ)FakePaymentsGateway(ローカル開発用)マイクロフレームワークは設定が各モジュールに散らばりがちです。これを避けましょう。
維持しやすいパターン:
こうすることで、コンポーネントの差し替えは配線レイヤーの小さな変更になります—残りのコードは安定したままです。
マイクロフレームワークは「唯一の正しい方法」を強制しません。代わりにルーティング、リクエスト/レスポンス処理、少数の拡張ポイントを提供し、チームの規模やプロダクトの成熟度に合ったパターンを採用できます。
馴染みのある「シンプルでクリーン」な構成です:コントローラがHTTP周りを扱い、サービスがビジネスルールを持ち、リポジトリがDBと会話します。
ドメインが単純でチームが小〜中規模、コードの置き場所を予測可能にしたい場合に合います。マイクロフレームワークは自然にこれを支援します:ルートはコントローラにマップされ、コントローラはサービスを呼び、リポジトリは軽量な手動構成で差し込まれます。
ヘキサゴナルアーキテクチャは、今の選択(DB、メッセージバス、外部API、UI)が将来変わることを見越すと有用です。
マイクロフレームワークはここでも相性がよく、"アダプタ"層はHTTPハンドラ+ドメインコマンドへの薄い変換であることが多いです。ポートはドメインのインタフェースで、アダプタがそれを実装します。フレームワークはエッジに留まります。
運用コストを増やさずにマイクロサービス的な明瞭さを得たい場合、モジュラー・モノリスは有力な選択です。1つのデプロイ単位を保ちながら、機能モジュール(Billing、Accounts、Notificationsなど)に分割し、明示的な公開APIを持たせます。
マイクロフレームワークは自動的にすべてを配線しないため、各モジュールが自分のルート、依存、データアクセスを登録でき、境界が見えやすく、誤って越えにくくなります。
これら3つのパターンに共通する利点は同じです:ルール(フォルダ構成、依存方向、モジュール境界)を自分で決められること。マイクロフレームワークは小さな安定した接続面を提供し、その上に自由に組み立てられます。
マイクロフレームワークは小さく始めて柔軟に保つのを助けますが、システムの“形”をどうするかは別問題です。正しい選択は技術よりもチームサイズ、リリース頻度、調整の痛み方に依存します。
モノリスは1つのデプロイ単位として出荷されます。1つのビルド、1つのログ、1か所でデバッグできるので最速でプロダクトを動かせます。
モジュラー・モノリスは依然として1つのデプロイですが内部が明確に分かれているため、コードベースが大きくなったときの"次の一手"として良い選択です。マイクロフレームワークならモジュールを明示的に保てます。
マイクロサービスはデプロイ単位を分割します。チーム間の結合を減らせますが、運用作業が増えます。
境界がすでに現実にあるときに分割すべきです:
フォルダが大きいというだけの理由や、サービスが同じDBテーブルを共有するような場合は分割を避けるべきです。それは安定した境界が見つかっていないサインです。
APIゲートウェイはクライアントを簡素化できます(エントリを1つにし、認証やレート制限を集中化)。しかし、賢くなりすぎるとボトルネックや単一障害点になるリスクがあります。
共有ライブラリは開発を高速化しますが、隠れた結合も生みます。複数サービスが同時にアップグレードを強いられるなら、分散モノリスを再現してしまいます。
マイクロサービスは継続的なコストを増やします:追加のデプロイパイプライン、バージョニング、サービスディスカバリ、モニタリング、トレーシング、インシデント対応、オンコールローテーションなど。チームがその運用を快適に回せないなら、マイクロフレームワーク部品で作ったモジュラー・モノリスの方が安全です。
マイクロフレームワークは自由を与えますが、保守性は設計しなければなりません。目標は「カスタム」の部分が見つけやすく、差し替えやすく、誤用しにくいことです。
1分で説明でき、コードレビューで守れる構成を選びます。一つの実用的な分割例:
app/(コンポジションルート:モジュールを配線)modules/(ビジネス機能)transport/(HTTPルーティング、リクエスト/レスポンスのマッピング)shared/(共通ユーティリティ:設定、ロギング、エラー型)tests/命名を一貫させます:モジュールフォルダは名詞(billing, users)、エントリポイントは予測可能に(index, routes, service)。
各モジュールを小さなプロダクトのように扱います:
modules/users/public.ts)modules/users/internal/*)modules/orders/internal/db.ts のような“貫通”インポートを別モジュールから行うのは避けてください。もし別モジュールが必要なら、公開APIに昇格させます。
小さなサービスでも基本的な可視性は必要です:
これらを shared/observability に置き、すべてのルートハンドラが同じ慣習を使うようにします。
クライアントにとって予測可能で、人的にデバッグしやすいエラーを作ります。共通のエラー形(code, message, details, requestId など)と各エンドポイントごとのスキーマを定義します。内部例外からHTTPレスポンスへのマッピングは中央に集約しておき、ハンドラはビジネスロジックに集中できるようにします。
素早く動きつつマイクロフレームワーク的なアーキテクチャを明示的に保ちたい場合、Koder.ai はスキャフォールディングと反復ツールとして有用です。置き換えではなく基盤を素早く作る手助けになります。モジュール境界、ミドルウェアスタック、エラーフォーマットをチャットで説明して、動作するベースアプリ(例:Reactフロント+Go + PostgreSQLバックエンド)を生成し、その配線を意図的に洗練していけます。
特にアーキテクチャ作業に合う2つの機能:
Koder.aiはソースコードのエクスポートをサポートするため、生成物はリポジトリで所有し、手作りプロジェクトと同じように進化させられます。
マイクロフレームワークベースのシステムは“手組み”に見えることが多く、テストはフレームワークの慣習よりも部品間の継ぎ目を守ることにフォーカスすべきです。目的はフルエンドツーエンドを毎回走らせることなく自信を持つことです。
まずビジネスルール(バリデーション、料金計算、権限ロジック)のユニットテストを行ってください。これが速く障害箇所を特定できます。
次に価値が高い少数の統合テストを投資します:配線(ルーティング→ミドルウェア→ハンドラ→永続化境界)を検証するテストです。これらはコンポーネントが合わさったときに起きる微妙なバグを捕まえます。
ミドルウェアは横断的な振る舞い(認証、ロギング、レート制限)を隠します。パイプラインとしてテストしてください:
ハンドラは内部呼び出しではなく公開されるHTTP形(ステータスコード、ヘッダ、レスポンスボディ)をテストすることを好みます。こうすることで内部が変わってもテストは安定します。
依存注入や単純なコンストラクタ引数を使って実際の依存をフェイクに差し替えます:
複数サービスやチームがAPIに依存する場合、契約テストを追加して期待されるリクエスト/レスポンスを固定化します。プロバイダ側の契約テストは、マイクロフレームワークの内部やモジュールが進化してもコンシューマを壊さないことを保証します。
マイクロフレームワークは自由を与えますが、自由は必ずしも明瞭さを生むわけではありません。チームが増えコードベースが拡大すると、「一時的」な決定が恒久化するリスクが出ます。
組み込みの慣習が少ないと、2つのチームが同じ機能を異なるスタイル(ルーティング、エラーハンドリング、レスポンス形式、ロギング)で実装してしまいがちです。不整合はレビューを遅くし、オンボーディングを難しくします。
簡単なガードレールが有効です:プロジェクト構成、命名、エラーフォーマット、ロギングフィールドを記した「サービステンプレート」ドキュメントを用意し、スターターリポジトリと数個のリンティングルールで強制します。
プロジェクトは最初は綺麗でも、utils/ フォルダが徐々に第2のフレームワークになっていくことがあります。モジュールがヘルパーや定数、グローバル状態を共有すると境界がぼやけ、変更が驚くような破壊を生みます。
明示的な共有パッケージ(バージョニング付き)を好むか、共有は最小限に留めます:型、インタフェース、よくテストされたプリミティブのみ。もしヘルパーがビジネスルールに依存するなら、それはドメインモジュールへ属すべきです。
認証、認可、入力検証、レート制限を手動で配線すると、ルートを見落としたりミドルウェアを忘れたり、ハッピーパスだけを検証してしまったりしがちです。
セキュリティのデフォルトを中央化してください:セキュアなヘッダ、一貫した認可チェック、エッジでのバリデーション。保護されたエンドポイントが確かに保護されていることをアサートするテストを追加しましょう。
無計画なミドルウェアの積み重ねはオーバーヘッドを招きます—特に複数のミドルウェアがボディをパースしたり、ストレージにアクセスしたり、ログをシリアライズしたりする場合。
ミドルウェアは小さく測定可能に保ちます。標準順序を文書化し、新しいミドルウェアを追加する際はコストをレビューします。ボトルネックが疑われるときはリクエストをプロファイルして冗長なステップを取り除きます。
マイクロフレームワークは選択肢を与えますが、選択にはプロセスが必要です。目標は「最高のアーキテクチャ」を見つけることではなく、チームが構築・運用・変更できる形を選ぶことです。
モノリスかマイクロサービスかを選ぶ前に次に答えてください:
不確かな場合は、マイクロフレームワークで構築したモジュラー・モノリスをデフォルトにしてください。境界を明確に保ちながら出荷は容易です。
マイクロフレームワークは一貫性を強制しないので、慣習を前もって決めて書き残します:
/docs に1ページの「サービス契約」を置くだけでも十分なことが多いです。
最初からほとんどのエンドポイントで必要になる横断モジュールを用意します:
これらは共有モジュールとして扱い、コピー&ペーストではなく再利用できるようにします。
アーキテクチャは要件に応じて変わるべきです。毎四半期、デプロイが遅くなっている箇所、スケール要件が異なる箇所、最も壊れる部分をレビューします。あるドメインがボトルネックになっているなら、それが次に分割すべき候補です—システム全体ではありません。
マイクロフレームワークのセットアップは通常「完璧に設計されている」わけではありません。1つのAPI、1つのチーム、タイトな納期で始まることが多く、価値は製品が成長するにつれて現れます:機能が増え、より多くの人がコードに触り、アーキテクチャは伸び縮みする必要があります。
最小のサービス(ルーティング、リクエストパース、1つのDBアダプタ)で始めます。多くのロジックはエンドポイント近くにありますが、それは出荷速度を優先したためです。
認証、支払い、通知、レポーティングを追加するにつれ、それらをモジュール(フォルダやパッケージ)に分離します。各モジュールは自分のモデル、ビジネスルール、データアクセスを所有し、外部に必要なものだけを公開します。
ロギング、認可チェック、レート制限、リクエストバリデーションはミドルウェアに移され、すべてのエンドポイントが一貫した振る舞いになります。順序が重要なので文書化しておくべきです。
文書化すること:
モジュールが内部を多く共有し始める、ビルド時間が目に見えて遅くなる、あるいは小さな変更が複数モジュールにまたがる必要が出てきたらリファクタのサインです。
チームが共有デプロイでブロックされる、異なる部分が異なるスケールを必要とする、あるいは統合境界が既に別プロダクトのように振る舞っているならサービス分割を検討します。
マイクロフレームワークは、所与のスタックに従うのではなくドメインに合わせてアプリケーションを形作りたい場合に適しています。明確さを利便性より重視するチーム—つまり、いくつかの重要なビルディングブロックを選び(そして維持し)ることを厭わないチーム—に特に向いています。
柔軟性を活かすために守るべき習慣:
2つの軽量アーティファクトから始めてください:
最後に、決定を行ったら文書化してください—短いメモでも役に立ちます。リポジトリに「Architecture Decisions」ページを置き、定期的に見直して昨日の近道が今日の制約にならないようにしましょう。
マイクロフレームワークは本質的な部分に集中します:ルーティング、リクエスト/レスポンス処理、基本的な拡張ポイント。
フルスタックフレームワークは通常、多くの「バッテリー同梱」機能(ORM、認証、管理画面、フォーム、バックグラウンドジョブなど)を含みます。マイクロフレームワークは利便性を犠牲にしてまで制御を提供します—必要なものだけを追加し、各パーツの接続方法を自分たちで決めます。
マイクロフレームワークは以下のような場合に適しています:
「最小の有用なコア」は通常次のとおりです:
まずはそこから始め、1つのエンドポイントを出荷してから、明確な価値があるときにモジュール(認証、バリデーション、観測性、キューなど)を追加します。
ミドルウェアは広く適用される横断的関心事に最適です。例:
ルートハンドラはビジネスロジックに集中させます:パース → サービス呼び出し → レスポンス返却。
オーダーは振る舞いを決めます。一般的で信頼できる順序は:
セットアップ付近に順序を文書化しておくと、将来の変更でレスポンスやセキュリティが壊れるのを防げます。
Inversion of Control は、ビジネスコードが自分で依存関係を作らない(“買いに行かない”)ことを意味します。代わりに、アプリの組み立て側が必要なものを渡します。
実務的には:起動時にデータベースクライアント、ロガー、APIクライアントを構築し、それらをサービス/ハンドラへ渡します。これにより強い結合が減り、テストや実装の差し替えが容易になります。
いいえ。多くのDI利点はシンプルなコンポジションルートで得られます:
依存関係グラフの管理が手作業で辛くなったらコンテナを検討しても遅くはありません—最初から複雑さを導入しないでください。
ストレージや外部APIを小さなインタフェース(ポート)の背後に置き、アダプターを実装します:
UserRepository(インタフェース): findById, create, listPostgresUserRepository(アダプタ): 本番用のPostgres実装見通しの良い境界を保つための実用的な構成例:
app/(コンポジションルート:モジュールを結線)modules/(機能モジュール:ドメイン能力)transport/(HTTPルーティング、リクエスト/レスポンスのマッピング)shared/(設定、ロギング、エラー型、観測性)ビジネスルール(バリデーション、価格計算、権限ロジック)のユニットテストを優先し、次に高価値の統合テストを少数用意して配線(ルーティング→ミドルウェア→ハンドラ→永続化境界)を検証します。
DI/フェイクを使って外部サービスを分離し、ミドルウェアはパイプラインとしてテストします。チームが増える場合は、APIの契約テストも導入して破壊的変更を防ぎます。
InMemoryUserRepositoryハンドラやサービスはインタフェースに依存し、具体実装には依存しません。データベースや外部プロバイダの切替は配線/設定変更だけで済みます。
tests/モジュールの公開API(例:modules/users/public.ts)を定め、内部への“貫通インポート”は避けます。