20211012のGoに関する記事は7件です。

AWSの強い権限のクレデンシャル管理から転生した件

クレデンシャルをパスワードみたい使ってない?  開発チームには最低限の権限付けたクレデンシャルを払い出し、その点ではセキュリティは確保しているものの、運用系には強い権限を持ち、かつトラブル対応時に期限が切れないように永続的な権限を払い出してはいないでしょうか。権限が強いので更新などを所有者に移譲しているため、定期更新なんかしないで使い続けてる管理になりがち・・ これではパスワードと何も変わらない! このツールはAWSでのクレデンシャル管理の概念を変えるため作られました 作ったわ! 詳しくはリポジトリで。バイナリをダウンロードして即使いたいならこっち どんなん? このツールを使うと・・ 強い権限を持つクレデンシャルを作らなくて済みます 更新期限を意識してクレデンシャルを運用しなくてすみます えええええええええ!?!? どういう事だってばよ!? GitのArchitectureより SSL & AES encrypted Credential by Token _____________ __________ ___________ |Server mode | ----> |Proxy mode| <---- |Client mode| ------------- ---------- ----------- |Cloudshell | Tokenized Data SSL & Dencrypt by Token ------------- このツールはだいぶトリッキーなAWSの仕組みを利用しています。 前提としてAWS CloudShellを起動し、以下を叩くと短期で強い権限のクレデンシャルが払い出されています。 curl -H"Authorization: $AWS_CONTAINER_AUTHORIZATION_TOKEN" $AWS_CONTAINER_CREDENTIALS_FULL_URI このクレデンシャルを中継してクライアント側で使っちまおう!というのがこのツールの発想 動きとか分かんないと怖いんすけど、、、 CloudShellとクレデンシャルを使いたい端末の両方からアクセス可能なネットワークでProxyモードでツールを起動させます。両方からのHTTPSアクセスを待ち続けます CloudShell上にこのツールをアップします(またはgitからダウンロードします)。ServerモードとしてProxyモードでツールが動いている端末に通信を開始します。この際、クレデンシャルはトークンを使い、AESで暗号化されるのでProxyに送られても生クレデンシャルは見えません クレデンシャルを使いたい端末からClientモードでProxyへアクセスします。この際にトークンを指定し、取得した文字列からクレデンシャルを複合します。複合できたらユーザープロファイル配下のクレデンシャルファイルを更新します CloudShellのクレデンシャルには期限があるので、期限が近づくとServerはProxyに新しいクレデンシャルを送り、ClientはProxyに向けて新しいクレデンシャルをリクエストします。これによりクレデンシャルは永続なのにも関わらず、中身は更新されていくのでセキュリティも強固になります。かつ、自動更新なのでユーザーは透過的に使用し続けられます やったね! は?CloudShellのブラウザはタイムアウトすんじゃん!  ここがこのツールのミソで、超簡易的なRPAの機能を内包しています。そのため、CloudShellを開いているブラウザに定期的にキーボードでエンターを叩いたのと同じ操作を行います。これがあるのでブラウザはタイムアウトせずにCloudShellへのアクセスは途絶えません。テストで二時間は確認した。 (ただ、RPA的な動きなので現時点でWindowsでしかこれは機能しないです。。) あとがき CloudShellがVPCに接続できないのでちょっち怖いなと思うかもだけど https://aws.amazon.com/jp/blogs/news/aws-cloudshell-command-line-access-to-aws-resources/ 現在、セッションはプライベート VPC サブネット内のリソースに接続できませんが、近日中に接続できるようになる予定です。  という事なのでこれが接続できるようになれば社内VPCで完全に閉じたネットワークでクレデンシャルを作成しないで運用できるってことになるわけです!そしたら、ちっこいWindowsインスタンスでこのツール動かしておけば強いクレデンシャル問題はマジで解決しそう・・
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HomebrewでのGoの環境構築(macOS)

環境 ・OS:macOS Big Sur ・シェル:zsh Goのインストール まず、ターミナルでこちらのコマンドを打って、Goをインストールします。 % brew install go GOROOTを設定 GOROOTとはGoのインストールパスです。 自分でインストールディレクトリを変更した場合には、環境変数$GOROOTの設定が必要です。 GOROOTは標準では/usr/local/goですが、homebrewでインストールした場合には/usr/local/opt/go/libexecになります。 % go env GOROOT /usr/local/opt/go/libexec GOPATHの設定 GOPATHはとは外部パッケージのリソースなどが保存されるパスです。GOPATHを設定しておくとコマンドを実行するときにフルパスを入れずに済みます。GOROOT同様に、GOPATH以下の/bin/にGoの実行バイナリが配置されるため、次のように$PATHを通します。 export GOPATH=$(go env GOPATH) export PATH=$PATH:$GOPATH/bin これで最低限のGoの開発準備を整えることができます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go+gRPCでシンプルなEchoサーバ(令和最新版)

はじめに GoのインストールからシンプルなEchoサービスをgRPCで実装するまでの手順。 各ツールのインストール Go $ wget https://golang.org/dl/go1.17.linux-amd64.tar.gz $ sudo tar xf go1.17.linux-amd64.tar.gz -C /usr/local $ echo "GOPATH=${HOME}/go" >>.bashrc $ echo "PATH=${PATH};/usr/local/go/bin" >>.bashrc $ source .bashrc protoc $ wget https://github.com/protocolbuffers/protobuf/releases/download/v3.18.1/protoc-3.18.1-linux-x86_64.zip $ unzip -d ${HOME}/.local protoc-3.18.1-linux-x86_64.zip $ echo "PATH=${PATH};${HOME}/.local/bin" >>.bashrc $ source .bashrc Goライブラリ $ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26 $ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1 $ go get -u google.golang.org/grpc Echoサービス実装 プロジェクトの雛形作成 $ mkdir -p myproj $ mkdir -p myproj/cmd/{client,server} $ mkdir -p myproj/internal/grpc $ export PROJ_ROOT=${HOME}/myproj $ cd ${PROJ_ROOT} $ go mod init 192.168.0.0/myproj .proto作成 $ cd ${PROJ_ROOT} $ vi my.proto syntax = "proto3"; option go_package = "192.168.0.0/myproj/grpc"; service Echo { rpc Echo (EchoRequest) returns (EchoReply) {} } message EchoRequest { string body = 1; } message EchoReply { string body = 1; } Goコード生成 $ protoc --go_out=./internal/grpc \ --go_opt=paths=source_relative \ --go-grpc_out=./internal/grpc \ --go-grpc_opt=paths=source_relative,require_unimplemented_servers=false my.proto サーバ実装 $ cd ${PROJ_ROOT}/cmd/server $ vi main.go package main import ( "context" "log" "net" "google.golang.org/grpc" mygrpc "192.168.0.0/myproj/internal/grpc" ) type server struct { } func (s *server) Echo(ctx context.Context, p *mygrpc.EchoRequest) (*mygrpc.EchoReply, error) { log.Println("request received") res := mygrpc.EchoReply{ Body: p.Body, } return &res, nil } func main() { lis, err := net.Listen("tcp", "127.0.0.1:9999") if err != nil { log.Fatalf("failed to listen: %v", err) } grpcServer := grpc.NewServer() mygrpc.RegisterEchoServer(grpcServer, &server{}) if err := grpcServer.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } } クライアント実装 $ cd ${PROJ_ROOT}/cmd/client $ vi main.go package main import ( "context" "log" "time" "google.golang.org/grpc" mygrpc "192.168.0.0/myproj/internal/grpc" ) func main() { conn, err := grpc.Dial("127.0.0.1:9999", grpc.WithInsecure()) if err != nil { log.Fatalf("connection failed: %v", err) } defer conn.Close() client := mygrpc.NewEchoClient(conn) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() r, err := client.Echo(ctx, &mygrpc.EchoRequest{Body:"hello"}) log.Println(r) log.Println("done") } 動作確認 サーバ起動 $ cd ${PROJ_ROOT}/cmd/server $ go run main.go クライアント実行 $ cd ${PROJ_ROOT}/cmd/client $ go run main.go 2021/10/12 18:06:47 body:"hello" 2021/10/12 18:06:47 done
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerを使ってGolangでHello,World!(超初心者向け)

Dockerでgolangを動かしてみます。 Dockerを触ったことがない人でもわかるように意識して書きました。 事前準備 ・Dockerのインストールが済んでいること ・Vimの基本的な操作(編集、保存、閉じる)ができること 本題 下記のコマンドを実行します。 docker run -it --name="任意の名前" -p 8080:80 golang:1.15 bash コマンドオプションの意味 -i :ホストの入力をコンテナの標準出力をつなげる -t :コンテナの標準出力とホストの出力をつなげる -i,-tオプションを付けないとコンテナの中で入力できない -iオプションと合わせて-itと記述可能 --name=" " :コンテナ名に任意の名前を付ける #付けなくても動きます -p 8080:80 :ポートマッピング  書き方:-p [ホスト側のポート番号]:[コンテナのポート番号]  -p 8080:80で localhost:8080 への接続が、コンテナ内のポート 80 へ送られます。 golang:1.15 :構築するイメージを指定 bash :コンテナ内で Bash シェルを起動 ローカルマシン ❯❯❯ docker run -it --name="first_app" -p 8080:80 golang:1.15 bash dockerコンテナの中にはいる Docker #Vimを使いたいのでupdate実行 root@51c29f39210b:/go# apt update Get:1 http://deb.debian.org/debian buster InRelease [122 kB] Get:2 http://security.debian.org/debian-security buster/updates InRelease [65.4 kB] ... #Vimをインストール root@51c29f39210b:/go# apt install vim Reading package lists... Done ... #Vimでmain.goファイルを作成 root@51c29f39210b:/go# vim main.go main.goファイル内に下記のような簡単なコードを書いてみる main.go package main import "fmt" func main() { fmt.Println("Hello, World!") } Vimを閉じてmain.goを実行する。 Docker root@51c29f39210b:/go# go run main.go Hello, World! Hello, World!を表示することができました。 おまけ  Dockerから出てもう一度接続する Dockerから出る Docker root@51c29f39210b:/go# exit 接続する ローカルマシン #コンテナの状態を確認する ❯❯❯ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 51c29f39210b golang:1.15 "bash" About an hour ago Exited (127) 3 seconds ago first_app #コンテナを起動する docker start <CONTAINER IDまたはNAME> ❯❯❯ docker start 51c29f39210b #コンテナに接続するdocker attach <CONTAINER IDまたはNAME> ❯❯❯ docker attach 51c29f39210b docker exec <CONTAINER IDまたはNAME>コマンドでも接続できる。 attachで接続した場合、exitコマンドで抜けるとコンテナが停止しますが、execで接続した場合、exitコマンドで抜けるてもコンテナが停止しないという違いがあります。 詳しくはこちらの記事に記載してあります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ぼくのレイヤードアーキテクチャ

はじめに サマリ Goを書くようになってからそろそろ1年が経ちます。 GoでWeb APIを実装する際の個人的パッケージ構成が定まりつつあるので備忘録を書き連ねます。 当初はMVCで実装していました。 最終的にレイヤードアーキテクチャを用いたパッケージ構成に落ち着きました1。 本稿ではDDD等の文脈は切り離して、パッケージ構成を責務毎に分割するための取り組みとしてのみレイヤ系アーキテクチャを取り扱います。 レイヤ系アーキテクチャとは? レイヤ構造を持ったアーキテクチャ(クリーン/オニオン/レイヤード...)等を指します。 MVCもレイヤ構造を持ちますが、今回はレイヤ系とは区別して説明いたします。 記事の内容 (主に)Web APIを実装する際に採用している個人的アーキテクチャについて説明します。 まず現在のレイヤードアーキテクチャに至る試行錯誤を話し、その後具体的なコードを交えながら解説します。 対象読者 レイヤ系アーキテクチャを理解する最初の一歩が欲しい方向けです。 レイヤ系アーキテクチャを極めるフローが下記の6段階あったとすると、自分は②と③の間くらいです。メチャクチャ赤ちゃんです。生暖かく見守っていただけると幸いです。 ① 既存パッケージ構成に負債が蓄積される → ② アーキテクチャの試行錯誤を開始する → ③ レイヤ系アーキテクチャを導入する(軽量DDD) → ④ DDDを完全理解する → ⑤ アーキテクチャの原理原則にこだわらなくなる、敢えてフラットパッケージを採用する → ⑥ ... MVCからスタートしてレイヤードに至った流れ それでは早速Web APIのアーキテクチャ試行錯誤についてお話します。 レイヤ系アーキテクチャを理解していなかった当初、ある程度手に馴染んだMVC2を用いて実装を行なっていました。 MVCを用いてWeb APIを実装する場合、 コントローラ - クライアントからのリクエストを受け付けモデルやビューを呼び出す処理を記述 モデル - 永続化対象のクラスや構造体、永続化の処理やロジックを記述 ビュー - JSON/HTML等を定義しレスポンスを返す処理を記述 みたいな感じになるかと思います。 シンプルなレスポンスの場合、ビューは作らずコントローラでそのまま返す場合も多いかも(自分も同様でした)。 余談ですが、MVCについてはGMOの成瀬さんの動画が神がかって分かりやすいです。 MVCをやっていく中で、いわゆるFat Controller/Fat Model問題にブチ当たりました。 ビュー無しのふたつのレイヤで処理をまかなっているので、コントローラかモデルのどちらかが大きな責務を担うことになります。 自分の場合はコントローラにたくさんゴニョゴニョ処理を書いており、可読性が低くテストもツライ感じに...。 そこで各種書籍を参考に、サービスレイヤをプラスした「MVC + S」アーキテクチャにリファクタしていきました。 コントローラにゴニョゴニョ書いていた処理を全てサービスレイヤに切り出し、コントローラは「入力を受け、サービスを呼び出し、レスポンスを返す」処理に終始します。 ビジネスロジックが全てサービスレイヤに剥がれたおかげでコントローラの見通しが良くなりました。 しかし、 サービスとモデルのどちらにロジック書くか迷う問題 「MVC + S」がメチャクチャメジャーなわけでない(要出典)ので人によって解釈がブレる 多層になるなら最早レイヤ系アーキテクチャでも良いのでは? 技術的な実装は別の層に切り出したい... 等々、悶々とする部分がありまして、最終的にはレイヤードアーキテクチャに落ち着いていきました。 導入したアーキテクチャ ここからは実際にWeb APIを実装する際に採用しているアーキテクチャを説明していきます。 成果物 コードがあった方が分かりやすいと思うので、こちらのリポジトリに格納しました。 パッケージ構成 パッケージ構成はこちらとほぼ(というか全く)同じです。 GoでのAPI開発現場のアーキテクチャ実装事例 / go-api-architecture-practical-example 責務の分割はレイヤードアーキテクチャ的にしつつ、ドメインとインフラの依存関係を逆転しています。 なので、正確には「レイヤード(っぽい)アーキテクチャ」です。 依存とは「AパッケージがBパッケージをimportしている状態(AがBに依存)」を指します。 レイヤードアーキテクチャではドメインがインフラをimportするのですが、本アーキテクチャではinterfaceを使って依存関係を逆転しています。 実際のパッケージ構成は下記の通りです。 . ├── application │   ├── create_user_service.go │   ├── find_user_service.go │   ├── notification_adapter.go │   └── ping_service.go ├── domain │   ├── user.go │   └── user_repository.go ├── handler │   ├── create_user_handler.go │   ├── find_user_handler.go │   ├── ping_handler.go │   └── shared.go ├── infra │   ├── firestore.go │   ├── slack_local_notifier.go │   └── user_repository_impl.go ├── main.go └── router.go なぜこのパッケージ構成に? 用語が独特なアーキテクチャは難しそうだったので(The Clean Architecture等)、ある程度直感的(?)なレイヤードアーキテクチャをベースにしています。 クリーンアーキテクチャなんてものはない ドメインとインフラの依存逆転の理由は、ただの一般論ですがテスタブルかつプラガブルにするためです3。 それと、本質ではないですがドメインに定義したモデルをリポジトリで利用可能になるのは実装上良かったです。 各レイヤの個人的方針メモ ここからは各層にどんなコードを記述したか、個人的な方針をメモ代わりにつらつらと書きます。 handler 入力の受け取りと出力を記述します。 craete_user_handler.goではなくuser_handler.goにCreateとかDeleteメソッドをぶらさげる例もありますが、共通化のメリットをそこまで感じなかったので前者を採用しました。 ハンドラーを呼び出すパブリックなメソッドはHandleにしました。 他の選択肢としてはRun等もあるかと思います(というか何でも良いと思います)。 application 処理のアウトライン(ビジネスロジック、ユースケース)を記述します。 ユースケースとは? アプリケーションに入力を与えるとき、何かしらの挙動をアプリケーションに期待しています。 例えば「新規ユーザの登録」をしたくてWeb APIを叩いたりするわけです。 ばっくり言えば、この「新規ユーザの登録」こそユースケースです。コード上は、新規ユーザを登録するために必要な「ユーザ情報を受け取り、DBに引き渡しクエリを発行し、結果を受け取る」といった一連のフローになります。 各ユースケースで再利用できる共通の処理があったとしても、ユースケースからユースケースを呼び出すのを禁止します。sharedディレクトリを切ったりSharedServiceを作る等して対応します。 ドメインにはモデル構造体に付随する程度のロジックのみを書くので、複雑なビジネスロジックが必要なユースケースがあれば、本レイヤ内に処理を分割して記述していきます(レイヤ内設計の重要性)。 domain いわゆるモデル等を格納するレイヤです。初心者実装だとDBのレコードと紐付くするアイツです4。あとはバリデーションロジックとかファクトリ(特殊なルールを持たせたコンストラクタみたいな感じ)とか、値オブジェクトやドメインサービス等を置いたりするレイヤです。 値オブジェクトは、特殊なルールを持った値をtype Foo string等と定義し、メソッドをぶら下げてバリデーションロジックを付与したりします。例えば「UserのIDは何桁以下にする」等のルールがあるのなら、プリミティブなstringで扱うのではなく、値オブジェクト化した方が便利です。 モデルを永続化する「リポジトリ」のインターフェースを定義するのもこのレイヤです。 本来domainというのはどこにも依存せず、参照される定義や、構造体に少しパラメーターの加工を担う関数が生えている程度に収めるべき goのパッケージ構成の失敗遍歴と現状確認 infra 永続化や通知等の技術的な実装5を書いていきます。 永続化は「アプリケーション外部に値を渡したり、受け取ったりすること」をおおよそ指すと理解しています。つまりDBに限りません。外部APIへのアクセスやSaaSへのアクセス等、その他諸々を含みます。 その他 基本的に別レイヤから呼び出されるパブリックメソッドは、ひとつのみにします(あくまで「基本的」に)。パブリックメソッドに与える引数はユースケースレイヤの場合XXXServiceInputやXXXServiceOutput等とし、レイヤをまたぐ時は独自の構造体(DTO)を定義するルールにします。これによりシステマティックに実装が可能です。 本来はWebアプリケーションFWも技術的な実装と捉えられますが、Web APIは割とFWの機能にべったりになったりもするので、直接main.goやroute.go、ハンドラーでFWをimportしています。原理原則よりも、自分がやりやすい形に落とし込めれば良いと感じています。 レイヤ系アーキテクチャではDIすることが多いです。動的にNewを行わず、コードの最初の方でDIを行い、全てのオブジェクトがFixした状態で処理を開始します。実行時エラーの危険性が減りますし、コードの見通しもよくなります。また、DI時にコード中で参照する変数も定義でき、一覧性が上がって分かりやすい面もあるかと思います(変数の注入がコードに散らばらない)。DIパッケージを切るのもありだと思います。 リポジトリについて リポジトリはリスト構造が持つようなメソッドを生やすのを基本にします(ドメイン駆動設計 モデリング/実装ガイド p69)。 こちらにリポジトリに生やすメソッドの一覧が載っていて分かりやすいです。 FindByXXX系がたくさん生えてきたらFindBySpecificationのように抽象化して、検索条件(XXXSpecification)を引数で与える方法もあるようです。 永続化に関するコードを記述するので、Slack通知もある意味「Slack上にメッセージを固定化する」という観点から見れば永続化です。ただし今回はSlackへのメッセージ自体にドメイン的な意味がないと考えたので、ユースケースレイヤにinterfaceを定義しインフラにはSlackLocalNotifierを定義して注入しています(詳細はドメイン駆動設計 モデリング/実装ガイド p73)。ただ、外部との連携部分は基本的に永続化と捉えられるケースが多いと思うので、全てリポジトリとして実装するのもアリかなとは感じています。 さいごに レイヤ構造に流行り廃りはあるようですが、一度基礎的な部分を理解しておくと、レイヤ構造を採用しないときでもエッセンスを利用できるな、と感じました。 レイヤ系アーキテクチャを理解すると、私のようにJava等を通っていない人間の場合、設計の基礎となる部分が身に付く気がしました。 それこそGUIアプリケーションを考えても、ボタンをポチッと押したコールバック関数が入力で6、ボタンを押すことで実現したい処理のアウトラインを書くところがユースケースで、ドメインロジックを書くところがあって、技術的な実装があって...といったように、レイヤ系アーキテクチャを流用して設計することも可能だと思われます。 参考資料 書籍 pospomeさんのアーキテクチャ本 特に1と4が良かったです。1はレイヤ構造の話で、4はレイヤ内の実装方針の話でした。レイヤ内の設計方針の資料は貴重な気がします。 ドメイン駆動設計 モデリング/実装ガイド DDDを実装する上での実践的なTipsが載っていて良いです。 Web Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える GoでのAPI開発現場のアーキテクチャ実装事例 ドメインとインフラの依存性を逆転しているので正確には「レイヤードアーキテクチャっぽいアーキテクチャ」です。 ↩ MVC2 ↩ コンテナ技術が一般的になり、モックしなくてもテストのしやすさは格段に上がっている面もあるとは思いますが...。 ↩ 例えばCQRSではCRUDのメソッドによってモデルを分けるのでこの説明は正しくありません。 ↩ 「技術的な実装」と見ると、ライブラリのラッパーとか抽象的なコードを書くものだと当初は考えてしまっていましたが、モデルを実際にDBに突っ込むような具体的な処理を書いていきます。 ↩ イベントこそがGUIアプリケーションの本質だとしたら、状態管理の変更検知も。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Go] MVCから始めて試行錯誤したパッケージ構成

はじめに サマリ Goを書くようになってからそろそろ1年が経ちます。 GoでWeb APIを実装する際の個人的パッケージ構成が定まりつつあるので備忘録を書き連ねます。 当初はMVCで実装していました。 最終的にレイヤードアーキテクチャを用いたパッケージ構成に落ち着きました1。 本稿ではDDD等の文脈は切り離して、パッケージ構成を責務毎に分割するための取り組みとしてのみレイヤ系アーキテクチャを取り扱います。 レイヤ系アーキテクチャとは? レイヤ構造を持ったアーキテクチャ(クリーン/オニオン/レイヤード...)等を指します。 MVCもレイヤ構造を持ちますが、今回はレイヤ系とは区別して説明いたします。 記事の内容 (主に)Web APIを実装する際に採用している個人的アーキテクチャについて説明します。 まず現在のレイヤードアーキテクチャに至る試行錯誤を話し、その後具体的なコードを交えながら解説します。 対象読者 レイヤ系アーキテクチャを理解する最初の一歩が欲しい方向けです。 レイヤ系アーキテクチャを極めるフローが下記の6段階あったとすると、自分は②と③の間くらいです。メチャクチャ赤ちゃんです。生暖かく見守っていただけると幸いです。 ① 既存パッケージ構成に負債が蓄積される → ② アーキテクチャの試行錯誤を開始する → ③ レイヤ系アーキテクチャを導入する(軽量DDD) → ④ DDDを完全理解する → ⑤ アーキテクチャの原理原則にこだわらなくなる、敢えてフラットパッケージを採用する → ⑥ ... MVCからスタートしてレイヤードに至った流れ それでは早速Web APIのアーキテクチャ試行錯誤についてお話します。 レイヤ系アーキテクチャを理解していなかった当初、ある程度手に馴染んだMVC2を用いて実装を行なっていました。 MVCを用いてWeb APIを実装する場合、 コントローラ - クライアントからのリクエストを受け付けモデルやビューを呼び出す処理を記述 モデル - 永続化対象のクラスや構造体、永続化の処理やロジックを記述 ビュー - JSON/HTML等を定義しレスポンスを返す処理を記述 みたいな感じになるかと思います。 シンプルなレスポンスの場合、ビューは作らずコントローラでそのまま返す場合も多いかも(自分も同様でした)。 余談ですが、MVCについてはGMOの成瀬さんの動画が神がかって分かりやすいです。 MVCをやっていく中で、いわゆるFat Controller/Fat Model問題にブチ当たりました。 ビュー無しのふたつのレイヤで処理をまかなっているので、コントローラかモデルのどちらかが大きな責務を担うことになります。 自分の場合はコントローラにたくさんゴニョゴニョ処理を書いており、可読性が低くテストもツライ感じに...。 そこで各種書籍を参考に、サービスレイヤをプラスした「MVC + S」アーキテクチャにリファクタしていきました。 コントローラにゴニョゴニョ書いていた処理を全てサービスレイヤに切り出し、コントローラは「入力を受け、サービスを呼び出し、レスポンスを返す」処理に終始します。 ビジネスロジックが全てサービスレイヤに剥がれたおかげでコントローラの見通しが良くなりました。 しかし、 サービスとモデルのどちらにロジック書くか迷う問題 「MVC + S」がメチャクチャメジャーなわけでない(要出典)ので人によって解釈がブレる 多層になるなら最早レイヤ系アーキテクチャでも良いのでは? 技術的な実装は別の層に切り出したい... 等々、悶々とする部分がありまして、最終的にはレイヤードアーキテクチャに落ち着いていきました。 導入したアーキテクチャ ここからは実際にWeb APIを実装する際に採用しているアーキテクチャを説明していきます。 成果物 コードがあった方が分かりやすいと思うので、こちらのリポジトリに格納しました。 パッケージ構成 パッケージ構成はこちらとほぼ(というか全く)同じです。 GoでのAPI開発現場のアーキテクチャ実装事例 / go-api-architecture-practical-example 責務の分割はレイヤードアーキテクチャ的にしつつ、ドメインとインフラの依存関係を逆転しています。 なので、正確には「レイヤード(っぽい)アーキテクチャ」です。 依存とは「AパッケージがBパッケージをimportしている状態(AがBに依存)」を指します。 レイヤードアーキテクチャではドメインがインフラをimportするのですが、本アーキテクチャではinterfaceを使って依存関係を逆転しています。 実際のパッケージ構成は下記の通りです。 . ├── application │   ├── create_user_service.go │   ├── find_user_service.go │   ├── notification_adapter.go │   └── ping_service.go ├── domain │   ├── user.go │   └── user_repository.go ├── handler │   ├── create_user_handler.go │   ├── find_user_handler.go │   ├── ping_handler.go │   └── shared.go ├── infra │   ├── firestore.go │   ├── slack_local_notifier.go │   └── user_repository_impl.go ├── main.go └── router.go なぜこのパッケージ構成に? 用語が独特なアーキテクチャは難しそうだったので(The Clean Architecture等)、ある程度直感的(?)なレイヤードアーキテクチャをベースにしています。 クリーンアーキテクチャなんてものはない ドメインとインフラの依存逆転の理由は、ただの一般論ですがテスタブルかつプラガブルにするためです3。 それと、本質ではないですがドメインに定義したモデルをリポジトリで利用可能になるのは実装上良かったです。 各レイヤの個人的方針メモ ここからは各層にどんなコードを記述したか、個人的な方針をメモ代わりにつらつらと書きます。 handler 入力の受け取りと出力を記述します。 craete_user_handler.goではなくuser_handler.goにCreateとかDeleteメソッドをぶらさげる例もありますが、共通化のメリットをそこまで感じなかったので前者を採用しました。 ハンドラーを呼び出すパブリックなメソッドはHandleにしました。 他の選択肢としてはRun等もあるかと思います(というか何でも良いと思います)。 application 処理のアウトライン(ビジネスロジック、ユースケース)を記述します。 ユースケースとは? アプリケーションに入力を与えるとき、何かしらの挙動をアプリケーションに期待しています。 例えば「新規ユーザの登録」をしたくてWeb APIを叩いたりするわけです。 ばっくり言えば、この「新規ユーザの登録」こそユースケースです。コード上は、新規ユーザを登録するために必要な「ユーザ情報を受け取り、DBに引き渡しクエリを発行し、結果を受け取る」といった一連のフローになります。 各ユースケースで再利用できる共通の処理があったとしても、ユースケースからユースケースを呼び出すのを禁止します。sharedディレクトリを切ったりSharedServiceを作る等して対応します。 ドメインにはモデル構造体に付随する程度のロジックのみを書くので、複雑なビジネスロジックが必要なユースケースがあれば、本レイヤ内に処理を分割して記述していきます(レイヤ内設計の重要性)。 domain いわゆるモデル等を格納するレイヤです。初心者実装だとDBのレコードと紐付くするアイツです4。あとはバリデーションロジックとかファクトリ(特殊なルールを持たせたコンストラクタみたいな感じ)とか、値オブジェクトやドメインサービス等を置いたりするレイヤです。 値オブジェクトは、特殊なルールを持った値をtype Foo string等と定義し、メソッドをぶら下げてバリデーションロジックを付与したりします。例えば「UserのIDは何桁以下にする」等のルールがあるのなら、プリミティブなstringで扱うのではなく、値オブジェクト化した方が便利です。 モデルを永続化する「リポジトリ」のインターフェースを定義するのもこのレイヤです。 本来domainというのはどこにも依存せず、参照される定義や、構造体に少しパラメーターの加工を担う関数が生えている程度に収めるべき goのパッケージ構成の失敗遍歴と現状確認 infra 永続化や通知等の技術的な実装5を書いていきます。 永続化は「アプリケーション外部に値を渡したり、受け取ったりすること」をおおよそ指すと理解しています。つまりDBに限りません。外部APIへのアクセスやSaaSへのアクセス等、その他諸々を含みます。 その他 基本的に別レイヤから呼び出されるパブリックメソッドは、ひとつのみにします(あくまで「基本的」に)。パブリックメソッドに与える引数はユースケースレイヤの場合XXXServiceInputやXXXServiceOutput等とし、レイヤをまたぐ時は独自の構造体(DTO)を定義するルールにします。これによりシステマティックに実装が可能です。 本来はWebアプリケーションFWも技術的な実装と捉えられますが、Web APIは割とFWの機能にべったりになったりもするので、直接main.goやroute.go、ハンドラーでFWをimportしています。原理原則よりも、自分がやりやすい形に落とし込めれば良いと感じています。 レイヤ系アーキテクチャではDIすることが多いです。動的にNewを行わず、コードの最初の方でDIを行い、全てのオブジェクトがFixした状態で処理を開始します。実行時エラーの危険性が減りますし、コードの見通しもよくなります。また、DI時にコード中で参照する変数も定義でき、一覧性が上がって分かりやすい面もあるかと思います(変数の注入がコードに散らばらない)。DIパッケージを切るのもありだと思います。 リポジトリについて リポジトリはリスト構造が持つようなメソッドを生やすのを基本にします(ドメイン駆動設計 モデリング/実装ガイド p69)。 こちらにリポジトリに生やすメソッドの一覧が載っていて分かりやすいです。 FindByXXX系がたくさん生えてきたらFindBySpecificationのように抽象化して、検索条件(XXXSpecification)を引数で与える方法もあるようです。 永続化に関するコードを記述するので、Slack通知もある意味「Slack上にメッセージを固定化する」という観点から見れば永続化です。ただし今回はSlackへのメッセージ自体にドメイン的な意味がないと考えたので、ユースケースレイヤにinterfaceを定義しインフラにはSlackLocalNotifierを定義して注入しています(詳細はドメイン駆動設計 モデリング/実装ガイド p73)。ただ、外部との連携部分は基本的に永続化と捉えられるケースが多いと思うので、全てリポジトリとして実装するのもアリかなとは感じています。 さいごに レイヤ構造に流行り廃りはあるようですが、一度基礎的な部分を理解しておくと、レイヤ構造を採用しないときでもエッセンスを利用できるな、と感じました。 レイヤ系アーキテクチャを理解すると、私のようにJava等を通っていない人間の場合、設計の基礎となる部分が身に付く気がしました。 それこそGUIアプリケーションを考えても、ボタンをポチッと押したコールバック関数が入力で6、ボタンを押すことで実現したい処理のアウトラインを書くところがユースケースで、ドメインロジックを書くところがあって、技術的な実装があって...といったように、レイヤ系アーキテクチャを流用して設計することも可能だと思われます。 参考資料 書籍 pospomeさんのアーキテクチャ本 特に1と4が良かったです。1はレイヤ構造の話で、4はレイヤ内の実装方針の話でした。レイヤ内の設計方針の資料は貴重な気がします。 ドメイン駆動設計 モデリング/実装ガイド DDDを実装する上での実践的なTipsが載っていて良いです。 Web Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える GoでのAPI開発現場のアーキテクチャ実装事例 ドメインとインフラの依存性を逆転しているので正確には「レイヤードアーキテクチャっぽいアーキテクチャ」です。 ↩ MVC2 ↩ コンテナ技術が一般的になり、モックしなくてもテストのしやすさは格段に上がっている面もあるとは思いますが...。 ↩ 例えばCQRSではCRUDのメソッドによってモデルを分けるのでこの説明は正しくありません。 ↩ 「技術的な実装」と見ると、ライブラリのラッパーとか抽象的なコードを書くものだと当初は考えてしまっていましたが、モデルを実際にDBに突っ込むような具体的な処理を書いていきます。 ↩ イベントこそがGUIアプリケーションの本質だとしたら、状態管理の変更検知も。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Go] MVCから始めて辿り着いたぼくのレイヤードアーキテクチャ

はじめに サマリ Goを書くようになってからそろそろ1年が経ちます。 GoでWeb APIを実装する際の個人的パッケージ構成が定まりつつあるので備忘録を書き連ねます。 当初はMVCで実装していました。 最終的にレイヤードアーキテクチャを用いたパッケージ構成に落ち着きました1。 本稿ではDDD等の文脈は切り離して、パッケージ構成を責務毎に分割するための取り組みとしてのみレイヤ系アーキテクチャを取り扱います。 レイヤ系アーキテクチャとは? レイヤ構造を持ったアーキテクチャ(クリーン/オニオン/レイヤード...)等を指します。 MVCもレイヤ構造を持ちますが、今回はレイヤ系とは区別して説明いたします。 記事の内容 (主に)Web APIを実装する際に採用している個人的アーキテクチャについて説明します。 まず現在のレイヤードアーキテクチャに至る試行錯誤を話し、その後具体的なコードを交えながら解説します。 対象読者 レイヤ系アーキテクチャを理解する最初の一歩が欲しい方向けです。 レイヤ系アーキテクチャを極めるフローが下記の6段階あったとすると、自分は②と③の間くらいです。メチャクチャ赤ちゃんです。生暖かく見守っていただけると幸いです。 ① 既存パッケージ構成に負債が蓄積される → ② アーキテクチャの試行錯誤を開始する → ③ レイヤ系アーキテクチャを導入する(軽量DDD) → ④ DDDを完全理解する → ⑤ アーキテクチャの原理原則にこだわらなくなる、敢えてフラットパッケージを採用する → ⑥ ... MVCからスタートしてレイヤードに至った流れ それでは早速Web APIのアーキテクチャ試行錯誤についてお話します。 レイヤ系アーキテクチャを理解していなかった当初、ある程度手に馴染んだMVC2を用いて実装を行なっていました。 MVCを用いてWeb APIを実装する場合、 コントローラ - クライアントからのリクエストを受け付けモデルやビューを呼び出す処理を記述 モデル - 永続化対象のクラスや構造体、永続化の処理やロジックを記述 ビュー - JSON/HTML等を定義しレスポンスを返す処理を記述 みたいな感じになるかと思います。 シンプルなレスポンスの場合、ビューは作らずコントローラでそのまま返す場合も多いかも(自分も同様でした)。 余談ですが、MVCについてはGMOの成瀬さんの動画が神がかって分かりやすいです。 MVCをやっていく中で、いわゆるFat Controller/Fat Model問題にブチ当たりました。 ビュー無しのふたつのレイヤで処理をまかなっているので、コントローラかモデルのどちらかが大きな責務を担うことになります。 自分の場合はコントローラにたくさんゴニョゴニョ処理を書いており、可読性が低くテストもツライ感じに...。 そこで各種書籍を参考に、サービスレイヤをプラスした「MVC + S」アーキテクチャにリファクタしていきました。 コントローラにゴニョゴニョ書いていた処理を全てサービスレイヤに切り出し、コントローラは「入力を受け、サービスを呼び出し、レスポンスを返す」処理に終始します。 ビジネスロジックが全てサービスレイヤに剥がれたおかげでコントローラの見通しが良くなりました。 しかし、 サービスとモデルのどちらにロジック書くか迷う問題 「MVC + S」がメチャクチャメジャーなわけでない(要出典)ので人によって解釈がブレる 多層になるなら最早レイヤ系アーキテクチャでも良いのでは? 技術的な実装は別の層に切り出したい... 等々、悶々とする部分がありまして、最終的にはレイヤードアーキテクチャに落ち着いていきました。 導入したアーキテクチャ ここからは実際にWeb APIを実装する際に採用しているアーキテクチャを説明していきます。 成果物 コードがあった方が分かりやすいと思うので、こちらのリポジトリに格納しました。 パッケージ構成 パッケージ構成はこちらとほぼ(というか全く)同じです。 GoでのAPI開発現場のアーキテクチャ実装事例 / go-api-architecture-practical-example 責務の分割はレイヤードアーキテクチャ的にしつつ、ドメインとインフラの依存関係を逆転しています。 なので、正確には「レイヤード(っぽい)アーキテクチャ」です。 依存とは「AパッケージがBパッケージをimportしている状態(AがBに依存)」を指します。 レイヤードアーキテクチャではドメインがインフラをimportするのですが、本アーキテクチャではinterfaceを使って依存関係を逆転しています。 実際のパッケージ構成は下記の通りです。 . ├── application │   ├── create_user_service.go │   ├── find_user_service.go │   ├── notification_adapter.go │   └── ping_service.go ├── domain │   ├── user.go │   └── user_repository.go ├── handler │   ├── create_user_handler.go │   ├── find_user_handler.go │   ├── ping_handler.go │   └── shared.go ├── infra │   ├── firestore.go │   ├── slack_local_notifier.go │   └── user_repository_impl.go ├── main.go └── router.go なぜこのパッケージ構成に? 用語が独特なアーキテクチャは難しそうだったので(The Clean Architecture等)、ある程度直感的(?)なレイヤードアーキテクチャをベースにしています。 クリーンアーキテクチャなんてものはない ドメインとインフラの依存逆転の理由は、ただの一般論ですがテスタブルかつプラガブルにするためです3。 それと、本質ではないですがドメインに定義したモデルをリポジトリで利用可能になるのは実装上良かったです。 各レイヤの個人的方針メモ ここからは各層にどんなコードを記述したか、個人的な方針をメモ代わりにつらつらと書きます。 handler 入力の受け取りと出力を記述します。 craete_user_handler.goではなくuser_handler.goにCreateとかDeleteメソッドをぶらさげる例もありますが、共通化のメリットをそこまで感じなかったので前者を採用しました。 ハンドラーを呼び出すパブリックなメソッドはHandleにしました。 他の選択肢としてはRun等もあるかと思います(というか何でも良いと思います)。 application 処理のアウトライン(ビジネスロジック、ユースケース)を記述します。 ユースケースとは? アプリケーションに入力を与えるとき、何かしらの挙動をアプリケーションに期待しています。 例えば「新規ユーザの登録」をしたくてWeb APIを叩いたりするわけです。 ばっくり言えば、この「新規ユーザの登録」こそユースケースです。コード上は、新規ユーザを登録するために必要な「ユーザ情報を受け取り、DBに引き渡しクエリを発行し、結果を受け取る」といった一連のフローになります。 各ユースケースで再利用できる共通の処理があったとしても、ユースケースからユースケースを呼び出すのを禁止します。sharedディレクトリを切ったりSharedServiceを作る等して対応します。 ドメインにはモデル構造体に付随する程度のロジックのみを書くので、複雑なビジネスロジックが必要なユースケースがあれば、本レイヤ内に処理を分割して記述していきます(レイヤ内設計の重要性)。 domain いわゆるモデル等を格納するレイヤです。初心者実装だとDBのレコードと紐付くするアイツです4。あとはバリデーションロジックとかファクトリ(特殊なルールを持たせたコンストラクタみたいな感じ)とか、値オブジェクトやドメインサービス等を置いたりするレイヤです。 値オブジェクトは、特殊なルールを持った値をtype Foo string等と定義し、メソッドをぶら下げてバリデーションロジックを付与したりします。例えば「UserのIDは何桁以下にする」等のルールがあるのなら、プリミティブなstringで扱うのではなく、値オブジェクト化した方が便利です。 モデルを永続化する「リポジトリ」のインターフェースを定義するのもこのレイヤです。 本来domainというのはどこにも依存せず、参照される定義や、構造体に少しパラメーターの加工を担う関数が生えている程度に収めるべき goのパッケージ構成の失敗遍歴と現状確認 infra 永続化や通知等の技術的な実装5を書いていきます。 永続化は「アプリケーション外部に値を渡したり、受け取ったりすること」をおおよそ指すと理解しています。つまりDBに限りません。外部APIへのアクセスやSaaSへのアクセス等、その他諸々を含みます。 その他 基本的に別レイヤから呼び出されるパブリックメソッドは、ひとつのみにします(あくまで「基本的」に)。パブリックメソッドに与える引数はユースケースレイヤの場合XXXServiceInputやXXXServiceOutput等とし、レイヤをまたぐ時は独自の構造体(DTO)を定義するルールにします。これによりシステマティックに実装が可能です。 本来はWebアプリケーションFWも技術的な実装と捉えられますが、Web APIは割とFWの機能にべったりになったりもするので、直接main.goやroute.go、ハンドラーでFWをimportしています。原理原則よりも、自分がやりやすい形に落とし込めれば良いと感じています。 レイヤ系アーキテクチャではDIすることが多いです。動的にNewを行わず、コードの最初の方でDIを行い、全てのオブジェクトがFixした状態で処理を開始します。実行時エラーの危険性が減りますし、コードの見通しもよくなります。また、DI時にコード中で参照する変数も定義でき、一覧性が上がって分かりやすい面もあるかと思います(変数の注入がコードに散らばらない)。DIパッケージを切るのもありだと思います。 リポジトリについて リポジトリはリスト構造が持つようなメソッドを生やすのを基本にします(ドメイン駆動設計 モデリング/実装ガイド p69)。 こちらにリポジトリに生やすメソッドの一覧が載っていて分かりやすいです。 FindByXXX系がたくさん生えてきたらFindBySpecificationのように抽象化して、検索条件(XXXSpecification)を引数で与える方法もあるようです。 永続化に関するコードを記述するので、Slack通知もある意味「Slack上にメッセージを固定化する」という観点から見れば永続化です。ただし今回はSlackへのメッセージ自体にドメイン的な意味がないと考えたので、ユースケースレイヤにinterfaceを定義しインフラにはSlackLocalNotifierを定義して注入しています(詳細はドメイン駆動設計 モデリング/実装ガイド p73)。ただ、外部との連携部分は基本的に永続化と捉えられるケースが多いと思うので、全てリポジトリとして実装するのもアリかなとは感じています。 さいごに レイヤ構造に流行り廃りはあるようですが、一度基礎的な部分を理解しておくと、レイヤ構造を採用しないときでもエッセンスを利用できるな、と感じました。 レイヤ系アーキテクチャを理解すると、私のようにJava等を通っていない人間の場合、設計の基礎となる部分が身に付く気がしました。 それこそGUIアプリケーションを考えても、ボタンをポチッと押したコールバック関数が入力で6、ボタンを押すことで実現したい処理のアウトラインを書くところがユースケースで、ドメインロジックを書くところがあって、技術的な実装があって...といったように、レイヤ系アーキテクチャを流用して設計することも可能だと思われます。 参考資料 書籍 pospomeさんのアーキテクチャ本 特に1と4が良かったです。1はレイヤ構造の話で、4はレイヤ内の実装方針の話でした。レイヤ内の設計方針の資料は貴重な気がします。 ドメイン駆動設計 モデリング/実装ガイド DDDを実装する上での実践的なTipsが載っていて良いです。 Web Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える GoでのAPI開発現場のアーキテクチャ実装事例 ドメインとインフラの依存性を逆転しているので正確には「レイヤードアーキテクチャっぽいアーキテクチャ」です。 ↩ MVC2 ↩ コンテナ技術が一般的になり、モックしなくてもテストのしやすさは格段に上がっている面もあるとは思いますが...。 ↩ 例えばCQRSではCRUDのメソッドによってモデルを分けるのでこの説明は正しくありません。 ↩ 「技術的な実装」と見ると、ライブラリのラッパーとか抽象的なコードを書くものだと当初は考えてしまっていましたが、モデルを実際にDBに突っ込むような具体的な処理を書いていきます。 ↩ イベントこそがGUIアプリケーションの本質だとしたら、状態管理の変更検知も。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む