Slide 1

Slide 1 text

サーバサイドDartを試してみた バベルの塔以前の世界へ ANDPAD Tech Live #2

Slide 2

Slide 2 text

1. 動機 2. なぜDartでサーバを実装するのか 3. サンプルの構成 4. サンプルのポイント解説 5. 感想 6. 実戦投入のはじめの一歩 目次 ※ FlutterやgRPCの基礎知識があることを前提にしている。本資料では、これらの詳細には触れない。

Slide 3

Slide 3 text

● サーバ、Webフロント、モバイルアプリで言語がばら ばらのせいか、相乗効果がない ○ バベルの塔の建設現場っぽい ● サーバのコードを読まないと、Web APIの仕様が分か らないことがある ○ モバイルアプリエンジニアからすると、サーバの言語とツー ルに馴染みがないのでつらい 1. 動機(問題意識) 主は、人の子らが作ろうとしていた街と塔とを見ようとしてお下りになり、そして仰せられた、「なるほど、彼らは一つの民で、同じ言葉を話して いる。この業は彼らの行いの始まりだが、おそらくこのこともやり遂げられないこともあるまい。それなら、我々は下って、彼らの言葉を乱して やろう。彼らが互いに相手の言葉を理解できなくなるように」。 — 「創世記」11章1-9節

Slide 4

Slide 4 text

● サーバ、Webフロント、モバイルアプリで言語を統一 する ○ ノウハウの共有ができる ○ 担当外のコードも読みやすい ○ フルスタック開発もできる 1. 動機(解決案)

Slide 5

Slide 5 text

● フルスタック開発ができる言語は2つ ○ Javascript(Typescript込み) ○ Dart ● モバイルアプリ開発では、Javascript(ReactNative)よ りも、Dart(Flutter)の方が勢いがある 2. なぜDartでサーバを実装するのか ※厳密にいえばGoなどでも全部開発できるが、この 2つの言語以外はロマンだと思う。

Slide 6

Slide 6 text

● サーバをDartで実装してみました ○ サンプルコード ■ https://github.com/KamikazeZirou/dart-ser ver-sample ○ おなじみのTodoアプリ ■ バックエンドのAPIのみ 3.サンプルの構成(概要)

Slide 7

Slide 7 text

3.サンプルの構成(アーキテクチャ) Repository MySQL Domain Model Service gRPC Handler API Client Handler:APIのエントリポイント。データ形式の変換をして、 gRPCを隠蔽する。 Service:ビジネスロジックを担当。ロジックが単純すぎるので Repositoryのメソッド呼ぶだけ。 DomainModel:データとビジネスロジックを持つ。サンプルは単純すぎるので単なる構造体。 Repository:データのCRUD機能などを提供。データの保存方法を隠蔽する。 円の外から内の依存関係は良いが、逆は NG。 Dart Server

Slide 8

Slide 8 text

ライブラリ バージョン 備考 Flutter 1.26.0.17.6.pre beta channel, Dart 2.12。 grpc 3.0.0 このバージョンではDart 2.12以上が必要。 protoファイルからのコード生成は割愛。 公式の説明をお勧めします。 https://grpc.io/docs/languages/dart/ mysql1 0.17.1 MySQLドライバ。 riverpod 0.13.0-nullsafety.3 DIに使う。 3.サンプルの構成(利用ライブラリ) mysql1以外は、モバイルアプリ開発でも珍しくはない

Slide 9

Slide 9 text

3.サンプルの構成(開発環境) ツール 説明 Docker MySQLサーバとDartサーバを動かす用。 Dockerを使わなくてもサーバは起動できるが、実務ではコンテナとしてデプロイ することが多いので Dockerを使う。 protoc gRPCのAPIを定義するprotoファイルから各言語のコードを生成するツール。 protoc_plugin protoファイルからDartのクライントコードとサーバコードを生成するツール。 Dart のプラグイン。 Android Studio コードエディタ。サーバ独自の設定は不要。 もちろん、InteliJやVSCodeでもOK。

Slide 10

Slide 10 text

3.サンプルの構成(開発環境) service TodoService { rpc CreateTodo (CreateTodoRequest) returns (CreateTodoResponse) {} } message Todo { int32 id = 1; string title = 2; string description = 3; } message CreateTodoRequest { // 作成するTodo Todo todo = 1; } message CreateTodoResponse { // 作成したTodo Todo todo = 1; } 補足:gRPCのコード生成 protoファイル Dart コード Go コード JS コード protocで 生成

Slide 11

Slide 11 text

4.サンプルのポイント解説(main) void main(List arguments) async { final container = ProviderContainer(); var listener = container.listen(todoServiceHandlerProvider); // 1. serviceHandler の作成 final serviceHandler = await listener.read(); // 2. serviceHandler をgRPCのServerに登録 final server = Server( [serviceHandler], const [], CodecRegistry(codecs: const [GzipCodec(), IdentityCodec()]), ); // 3. サーバ起動 await server.serve(port: 5001); ... }

Slide 12

Slide 12 text

4.サンプルのポイント解説(Handler) // 1. Handlerにはprotocが生成したAPIサーバ用の基底クラスを継承させる class TodoServiceHandler extends TodoServiceBase { // 2. APIに対応するメソッドがあるのでoverrideする @override Future createTodo(ServiceCall call, grpc.CreateTodoRequest request) async { // 3. データをgRPC -> domain形式に変換 // 4. Serviceのメソッドを呼ぶ final newTodo = await _todoService.create(domain.Todo( title: request.todo.title, description: request.todo.description, )); // 5. Serviceの処理結果をgRPC形式に変換して返す return CreateTodoResponse( todo: grpc.Todo( id: newTodo.id, title: newTodo.title, description: newTodo.description, )); } } 形式変換をするのは、 Service がgRPCに依存しないようにす るため。

Slide 13

Slide 13 text

● Service ○ サンプルではRepositoryのAPI呼ぶだけ ● Repository ○ モバイルアプリの時と同じように実装すればOK 4.サンプルのポイント解説(Service/Repository)

Slide 14

Slide 14 text

riverpodを使用。 Handler、Service、 Repositoryのオブジェクト のProviderを実装。 // MySQLConnection のプロバイダ final dbConnectionProvider = Provider.autoDispose((ref) async { ... final connection = await MySqlConnection.connect(settings); ... return connection; }); // Repository のプロバイダ。 // ref.watch で依存オブジェクトを取得する。 // ここでは、MySQLConnection を取得している。 final todoRepositoryProvider = Provider.autoDispose((ref) => ref.watch(dbConnectionProvider).then((conn) => MySQLTodoRepository(conn))); final todoServiceProvider = Provider.autoDispose((ref) => ref .watch(todoRepositoryProvider) .then((repository) => TodoServiceImpl(repository))); final todoServiceHandlerProvider = Provider.autoDispose((ref) => ref .watch(todoServiceProvider) .then((service) => TodoServiceHandler(service))); 4.サンプルのポイント解説(DI)

Slide 15

Slide 15 text

マルチステージビルドを使う。 ビルド用と実行用でイメージを分け ることで、実行イメージのサイズを 抑える手法。 ビルドに使うDockerイメージのサイ ズは約250MBある。Dart SDKなど が含まれるため。 実行用のDockerイメージは、開発 用のツールが一切入っていないの でサイズは約2.2MB。 (ビルドしたバイナリファイルのサイ ズは約8.7MB) 4.サンプルのポイント解説(Docker) # ランタイムなしで実行可能なサーバのバイナリファイルをビルドするイメージ FROM google/dart:2.12-beta as builder WORKDIR /app ADD pubspec.* /app/ RUN pub get ADD . /app RUN pub get --offline RUN dart compile exe bin/server.dart -o bin/server # ビルドしたバイナリを実行するイメージ FROM subfuzion/dart:slim WORKDIR /app COPY --from=builder /app/bin/server . CMD [] ENTRYPOINT ["./server"]

Slide 16

Slide 16 text

4.サンプルのポイント解説(docker-compose) ● Docker単独よりもサーバの実行が簡単にできる ○ サーバの起動時のコマンドがDockerコマンドよりも簡潔に なる ○ コンテナ間の依存関係を設定できる ■ DBのコンテナをDartサーバのコンテナより先に実行できる

Slide 17

Slide 17 text

4.サンプルのポイント解説(client) Future main(List args) async { // 1. gRPC接続チャンネルを作る final channel = ClientChannel( 'localhost', port: 50051, options: ChannelOptions( credentials: ChannelCredentials.insecure(), codecRegistry: CodecRegistry(codecs: const [GzipCodec(), IdentityCodec()]), ), ); // 2. protoファイルから生成したClientのオブジェクトを作る final stub = TodoServiceClient(channel); // 3. gRPCのAPIを呼ぶ。 try { final response = await stub.createTodo( CreateTodoRequest( todo: Todo(title: 'title3', description: 'description3')), options: CallOptions(compression: const GzipCodec()), ); } catch (e) { ... } await channel.shutdown(); }

Slide 18

Slide 18 text

● 良かったこと ○ アプリ開発とノウハウは共有できる ■ riverpodは初めて使ったが、モバイルアプリ開発にも知見を転用でき る ○ Dartの書き方をほぼ忘れていたが、Goよりは書きやすい ■ 並列処理が出てきたら、感想は変わりそう ● 懸念 ○ サーバ向けライブラリが充実していない ■ 有力なORMが少ない ● Web Framework Aqueductの一部としてならORMあるが・・・ ■ AWSの公式SDKはなさそう・・・ 5.感想

Slide 19

Slide 19 text

● モバイルアプリ用のAPI Gatewayで採用する 6.実戦投入のはじめの一歩 BFFでは、API GatewayはAPIクライアント ごとの要件に沿って実装する。 開発の担当は、APIクライアント開発者が 良いらしい。クライアントの要件に詳しいの は、クライアントの開発者のため。 モバイルアプリ開発者が慣れている Dart をAPI Gatewayに採用すると、開発効率 は上がるはず。 ※今のANDPADにはAPI Gatewayなし

Slide 20

Slide 20 text

ANDPADでは、マイクロサービスを開発する メンバーを募集しています! こんな方におすすめです。 ● アプリ開発以外もやってみたい ● ユーザの業務を考えつつ開発をしたい ● サービスの基盤を考えたい 最後に

Slide 21

Slide 21 text

番外 言語 MSにたとえると 補足 Kotlin ゲルググ Dart リック・ドムⅡ C# ドワッジ Java ドム Go イフリート グフとドムの中間。 Rust グフ ゲームだと格闘が強い。使ったことはない。 C++ ザク C 旧ザク GCの壁