Share
Embed
Serverlessだからこそコードと設計にはこだわろう
2025/05/10に開催された、Serverless meetup fukuoka #5 での登壇資料です。
サーバーレスでプログラムを動かすための環境を意識しないでいいのなら、その分コードや設計にこだわろうというお話です。
https://serverless.connpass.com/event/348712/
Ken'ichirou Kimura
May 10, 2025
Transcript
26 slides
-
Copyright © 2015-2025 ALTERBOOTH inc. All Rights Reserved. Serverlessだからこそ コードと設計にはこだわろう
木村健一郎 2025年5月10日 Serverless Meetup Fukuoka #5 -
Copyright © 2015-2025 ALTERBOOTH inc. All Rights Reserved. 名前 :木村健一郎(しょーちゃん)
所属 :株式会社オルターブース SORACOM UG九州 JAWS-UG福岡 娘ちゃんのパパ(7歳10ヶ月) お仕事:IoT番長 受賞歴:SORACOM MVC 2021,2023 AWS Samurai 2019 APJ Commnity Award 2023(Ownership) 好きなAzureサービス :IoT Hub、Functions、Cosmos DB 好きなAWSサービス :Lambda、App Runner、IoT Core SNS : @show_m001 -
Copyright © 2015-2025 ALTERBOOTH inc. All Rights Reserved. Serverlessとは? 改めておさらい。Serverlessとは?
ChatGPT(4o)に聞いてみた。 アプリケーション開発者がサーバーの構築・運用を意識せずにコードを実行できるクラウドコンピューティングの モデルです。 ただし、「サーバーが存在しない」という意味ではありません。 サーバーの管理をクラウドプロバイダー(AWS, Azure, Google Cloudなど)に完全に任せるという意味です。 -
Copyright © 2015-2025 ALTERBOOTH inc. All Rights Reserved. つまり •
動かすための環境を意識しないでいい • 自分のやりたいこと=解決したい課題に集中できる ということは、コーディングで言えば「ビジネスロジック」に集 中できる、といえます -
Copyright © 2015-2025 ALTERBOOTH inc. All Rights Reserved. こんなコード書いてませんか? export
const handler = async (event, context) => { if (checkApiToken(event.headers[‘authorization’].split/’Bearer ‘/[1] )) === false ){ return { “statusCode”: 401, } } const result = MyService.businessRogic(event.context); } return { "statusCode": 200, "headers": { "Content-Type": "application/json" }, "body": JSON.stringify(result), } } -
Copyright © 2015-2025 ALTERBOOTH inc. All Rights Reserved. このコードのよくないところ •
パラメータの取得がこれでOKなのはLambdaだからですよ ね? • ビジネスロジックにLambdaのイベント丸渡しは手抜き過ぎま せんか? • 戻り値のHTTPレスポンスの詰め方がこれでOKなのはLambda だからですよね? -
Copyright © 2015-2025 ALTERBOOTH inc. All Rights Reserved. 考えてみてください •
せっかくサーバーのことを考えないでよくなったのに、まだ実 行環境依存部分がビジネスロジックに混在したコードを書くん ですか? • もしLambdaの仕様が変わったり、Lambda以外で動かさない といけなくなったときあちこち書き直すんですか? • どこ書き直せばいいか把握できてますか? -
Copyright © 2015-2025 ALTERBOOTH inc. All Rights Reserved. 先人の知恵に学ぶ 先人は言いました
• 関心事は分離しろ • 依存部分は分離しろ • 依存の方向は逆転させろ • テスト容易性を確保しろ • 変更に強くしろ そう、SOLID原則とかクリーンアーキテクチャとかのことです。 実装の詳細≒環境依存の部分はできるだけコアロジックから分離すべ きなのです。 -
Copyright © 2015-2025 ALTERBOOTH inc. All Rights Reserved. どういうもの? •
3月にサービス終了した、LINE Notifyの代替サービス • LINE Messaging APIを使ったボットプログラム • https://{ボットのEndpoint}/notify というURLにこれまでの LINE Notifyと同じように送信すると、ボットが参加してるグ ループまたはボットの友達全員に通知する • LINE Notifyの入力形式は一通り実装してるはずなので、画像を アップロードして送ることも可能 -
Copyright © 2015-2025 ALTERBOOTH inc. All Rights Reserved. 特徴 •
マルチクラウド対応(AWS/Azure/cloudflare) • レイヤードアーキテクチャで作ってるから他のクラウドサービス版を 作るのも容易 • IaCも準備してるのでデプロイも楽ちん • AWSならcdk deploy一発 • Azureなら「Deploy to Azure」ボタンとGitHub Actionsで • cloudflareならterraformとwranglerで • ライセンスはMIT -
Copyright © 2015-2025 ALTERBOOTH inc. All Rights Reserved. アーキテクチャ概要(Lambda) Lambda
handler IImageConverter IImageStorage IGroupRepository LineNotifyMesse ngerApp S3ImageStorage JimpImageConvert er LineService DynamoGroupRep ository use use use IHttpRequestHandler LambdaHttpReque stHandler -
Copyright © 2015-2025 ALTERBOOTH inc. All Rights Reserved. Lambda handler
IImageConverter IImageStorage IGroupRepository LineNotifyMesse ngerApp S3ImageStorage JimpImageConvert er LineService DynamoGroupRep ository use use use IHttpRequestHandler LambdaHttpReque stHandler ハンドラーは実行環境に対するエンドポイントの提供と、各サービスのDIのみ行う -
Copyright © 2015-2025 ALTERBOOTH inc. All Rights Reserved. Lambda handler
IImageConverter IImageStorage IGroupRepository LineNotifyMesse ngerApp S3ImageStorage JimpImageConvert er LineService DynamoGroupRep ository use use use IHttpRequestHandler LambdaHttpReque stHandler ビジネスロジックは、純粋なロジックのみで特定の環境に依存しない -
Copyright © 2015-2025 ALTERBOOTH inc. All Rights Reserved. Lambda handler
IImageConverter IImageStorage IGroupRepository LineNotifyMesse ngerApp S3ImageStorage JimpImageConvert er LineService DynamoGroupRep ository use use use IHttpRequestHandler LambdaHttpReque stHandler LINE Messaging APIとやり取りするサービスクラスはLINE Messaging APIにのみ依存する -
Copyright © 2015-2025 ALTERBOOTH inc. All Rights Reserved. Lambda handler
IImageConverter IImageStorage IGroupRepository LineNotifyMesse ngerApp S3ImageStorage JimpImageConvert er LineService DynamoGroupRep ository use use use IHttpRequestHandler LambdaHttpReque stHandler ビジネスロジックは抽象化したインターフェースを介して環境に依存する処理を呼び出す -
Copyright © 2015-2025 ALTERBOOTH inc. All Rights Reserved. Lambda handler
IImageConverter IImageStorage IGroupRepository LineNotifyMesse ngerApp S3ImageStorage JimpImageConvert er LineService DynamoGroupRep ository use use use IHttpRequestHandler LambdaHttpReque stHandler 環境に依存する処理は各サービスクラスがインターフェースを実装する -
Copyright © 2015-2025 ALTERBOOTH inc. All Rights Reserved. Lambda handler
IImageConverter IImageStorage IGroupRepository LineNotifyMesse ngerApp S3ImageStorage JimpImageConvert er LineService DynamoGroupRep ository use use use IHttpRequestHandler LambdaHttpReque stHandler つまり、別の環境で動かす場合は環境依存の部分だけを修正(別途実装/DI)すればよく、 -
Copyright © 2015-2025 ALTERBOOTH inc. All Rights Reserved. Lambda handler
IImageConverter IImageStorage IGroupRepository LineNotifyMesse ngerApp S3ImageStorage JimpImageConvert er LineService DynamoGroupRep ository use use use IHttpRequestHandler LambdaHttpReque stHandler コアロジックには一切の変更が不要 -
Copyright © 2015-2025 ALTERBOOTH inc. All Rights Reserved. Lambdaハンドラーのコード(抜粋) export
const handler = async (event) => { // 環境変数のチェック const lineChannelAccessToken = process.env.LINE_CHANNEL_ACCESS_TOKEN; if (!lineChannelAccessToken) { throw new Error('LINE_CHANNEL_ACCESS_TOKEN is not set'); } …. // 依存関係を登録 container.registerInstance('IImageStorage', new S3ImageStorage(bucketName, s3Region)); …. const app = container.resolve(LineNotifyMessengerApp); // リクエスト処理を委譲し、結果を返す return await app.processRequest(); }; -
Copyright © 2015-2025 ALTERBOOTH inc. All Rights Reserved. Azure Functionsハンドラーのコード(抜粋)
export async function HttpTrigger(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> { // 環境変数のチェック const lineChannelAccessToken = process.env.LINE_CHANNEL_ACCESS_TOKEN; if (!lineChannelAccessToken) { throw new Error('LINE_CHANNEL_ACCESS_TOKEN is not set'); } …. // 依存関係を登録 container.registerInstance('IImageStorage', new BlobImageStorage(blobConnecitonString, blobName)); …. const app = container.resolve(LineNotifyMessengerApp); // リクエスト処理を委譲し、結果を返す return await app.processRequest() as AzureFunctionsHttpResponse; }; エンドポイントの 関数シグネチャ(外 界とのインター フェース)はしょう がない DIするものは当然 変わる 戻り値(外界とのイ ンターフェース)は 明示的にキャスト して変更 -
Copyright © 2015-2025 ALTERBOOTH inc. All Rights Reserved. cloudflare workersハンドラーのコード(抜粋)
export default { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { // 環境変数のチェック const lineChannelAccessToken = process.env.LINE_CHANNEL_ACCESS_TOKEN; if (!lineChannelAccessToken) { throw new Error('LINE_CHANNEL_ACCESS_TOKEN is not set'); } …. // 依存関係を登録 container.registerInstance('IImageStorage', new R2ImageStorage(r2Bucket, origin)); …. const app = container.resolve(LineNotifyMessengerApp); // リクエスト処理を委譲し、結果を返す return await app.processRequest() as Response; }; -
Copyright © 2015-2025 ALTERBOOTH inc. All Rights Reserved. メリット •
コードが理解しやすい • ロジックが凝集してるので、そこだけ見ればよい • 関係の無い処理が含まれてないので理解しやすい • 他の環境に持って行きやすい • プラットフォームの変更(クラウドプロバイダーだけではなく、コンテナに変 える、RDBをNoSQLにしたいなど)も容易 • ロジックの再利用も容易 • テストしやすい • コアロジックが実装の詳細に依存しないので自動テストが書きやすい • 壊れにくい • 環境依存部分の修正がコアロジックに影響しない • 自動テストがあれば壊れてもすぐ気づける -
Copyright © 2015-2025 ALTERBOOTH inc. All Rights Reserved. デメリット/反論 •
コードが煩雑 • 慣れていないと読みにくい • 書き始めが大変 • フットプリントが大きい • そもそも再利用や他のプラットホームへの変更なんてする? • まぁそこはプロダクト次第ですが・・ • サーバレスでちょっとしたもの書きたいだけなのにいつもここまでやる の? • 本体が数十行程度なら抽象化のためのコードの方が多くなっちゃうから確かに無駄か も知れませんが、この知見は一定以上の規模のプロジェクトに生きるはずです • 再利用時もAIが書きなおしてくれるでしょ? • AIも間違いを犯す以上、書き直すところは最小である方が当然壊れにくい • テストしやすさはAIの書いたコードが正しいかの検証になる -
Copyright © 2015-2025 ALTERBOOTH inc. All Rights Reserved. まとめ •
Serverlessで環境を意識しないでいいのなら、その分コードと 設計にこだわりませんか? • 今一度SOLID原則やクリーンアーキテクチャを見直してみましょう • 習作として作成したLINE Notify互換のボットプログラム • レイヤードアーキテクチャでできるだけSOLIDに沿うように作ってい ます • その結果、マルチクラウド(Azure/AWS/cloudflare)に対応してるの で好きな環境で動かせます • そこまでこだわる必要が無いケースも多いですが、大きなプロ ダクトになるときに必ずこの知見は生きると思っています