Slide 1

Slide 1 text

Serverpodを活用した Dartのフルスタックアプリケーション開発 2024/03/19 吉村 拓海

Slide 2

Slide 2 text

⾃⼰紹介 吉村 拓海 (Yoshimura Takumi) 株式会社サイバーエージェント 2023年5⽉ 中途⼊社 AI事業本部 アプリ運⽤カンパニー所属 モバイルアプリエンジニア

Slide 3

Slide 3 text

アプリ運用カンパニーとは ● 小売企業様と協業して、アプリを中心としたデジタルでの購買体験の実 現を行う事業部

Slide 4

Slide 4 text

● Serverpodとは ● Serverpodの使用方法 ● Serverpodの主な機能 ● まとめ アジェンダ

Slide 5

Slide 5 text

Serverpodとは

Slide 6

Slide 6 text

Serverpodとは ● FlutterコミュニティのためにDartで書かれたアプリケーションサーバー ● Dartでフルスタックにアプリケーションの開発ができる ● サーバーサイドの実装をより簡潔に素早く実装できるようにすることを 目的に設計しており、多くのモジュールや仕組みが提供されている 今回は、実際にServerpodを利用してみた内容をお話しします。 (機能が多く網羅的ではないですが、ご了承ください🙇)

Slide 7

Slide 7 text

● Serverpodの使用方法

Slide 8

Slide 8 text

Serverpodの使用方法 Serverpodの使用方法 1. Serverpod cli のインストール 2. Serverpod プロジェクトの作成 $ dart pub global activate serverpod_cli $ serverpod create mypod mypod_server mypod_client mypod_flutter パッケージ が⽣成される

Slide 9

Slide 9 text

各パッケージの役割について Serverpodの使用方法 エンドポイントやWEBサーバーの実装、モデルやプロトコルの定義を持つ。 mypod_server mypod_client mypod_flutter サーバーと通信するためのクライアントの実装を持つ。 このパッケージはサーバー側の定義からコードが自動生成されるため実装不要。 Flutterアプリケーションの実装を持つ。clientパッケージに依存している。

Slide 10

Slide 10 text

3. Serverpodの実行 ● アプリケーションサーバー ○ Flutterアプリがアクセスするためのサーバー ● Serverpod insights サーバー ○ サーバーの状態やログを確認するアプリケーションのためのサーバー ● WEBサーバー ○ アプリケーションサーバーにアクセス可能なWEBページやAPIを設定可能なサーバー ■ まだ実験段階のため、API等が大きく変更される可能性あり Serverpodの使用方法 $ cd mypod/mypod_server $ docker compose up --build --detach $ dart bin/main.dart Local DB(Postgres), Cache(Redis)を利⽤ するため、dockerが必要になります

Slide 11

Slide 11 text

● Serverpodの主な機能

Slide 12

Slide 12 text

Serverpodの主な機能 ● コードの自動生成 ● ORMとリレーション ● DBマイグレーション ● 認証 ● ファイルアップロード ● サーバーの可視化 ● データストリーミング ● キャッシュ ● タスクスケジューリング ● ヘルスチェック ● 簡単なデプロイ ● ビルトインのWEBサーバー ● など

Slide 13

Slide 13 text

Serverpodの主な機能 ● コードの自動生成 ● ORMとリレーション ● DBマイグレーション ● 認証 ● ファイルアップロード ● サーバーの可視化 ● データストリーミング ● キャッシュ ● タスクスケジューリング ● ヘルスチェック ● 簡単なデプロイ ● ビルトインのWEBサーバー ● など こちら側の機能を紹介します

Slide 14

Slide 14 text

Serverpodの主な機能 ● コードの自動生成 ● ORMとリレーション ● DBマイグレーション ● 認証 ● ファイルアップロード ● サーバーの可視化 ● データストリーミング ● キャッシュ ● タスクスケジューリング ● ヘルスチェック ● 簡単なデプロイ ● ビルトインのWEBサーバー

Slide 15

Slide 15 text

サーバー側で実装したモデルやエンドポイントから、サーバー側のルーティン グやクライアントのコードを自動生成できる。 ● サーバー・アプリ間のプロトコルやモデルが共通で利用できる ● サーバー側のエンドポイントのルーティングの実装が不要 ● 生成されたクライアントを利用することで、メソッドコールするようにエンド ポイントへのアクセスができる コードの自動生成

Slide 16

Slide 16 text

例) エンドポイントの実装からコードを自動生成する 1. サーバー側のエンドポイントを実装 2. $ serverpod generate を実行 server/lib/src/generated 以下にサーバー側のコード、client/lib/src/protocol 以下にクライアント 側のコードが生成される。 class ExampleEndpoint extends Endpoint { Future hello(Session session, String name) async { return 'Hello $name'; } } コードの自動生成

Slide 17

Slide 17 text

生成されたクライアントのメソッドをFlutterアプリから呼び出すことで、実 装したエンドポイントへのアクセスができる。 コードの自動生成 // 自動生成されたクライアントクラス final client = Client('https://myapi.com/'); // エンドポイントのアクセス final result = await client.example.hello('Serverpod'); // 'Hello Serverpod' print(result); class ExampleEndpoint extends Endpoint { Future hello(Session session, String name) async { return 'Hello $name'; } } エンドポイントでは引数 sessionを利⽤することで、 以下へのアクセスが可能になる ● HTTPリクエストの情報 ● 認証情報 ● データベース ● キャッシュ ● ストレージ

Slide 18

Slide 18 text

コードの自動生成 エンドポイントの引数や戻り値にはStringやint等に加えて、独自のモデルや エラーを作成して利用することもできる。 class: Example fields: message: String createdAt: DateTime exception: InvalidArgumentException fields: message: String // Server code Future hello(Session session, String name) async { if (name.isEmpty) { throw InvalidArgumentException(message: 'Name cannot be empty'); } return Example(message: 'Hello $name', createdAt: DateTime.now()); } // Flutter code // InvalidArgumentExceptionが発生 final result = await client.example.hello(''); // Exampleオブジェクトが取得できる final result2 = await client.example.hello('Serverpod');

Slide 19

Slide 19 text

コードの自動生成 classやenum、exceptionのモデルが作成できる。 以下の型やNull許容をサポートし、生成したモデルを使用することも可能。 ● bool ● int ● double ● String ● Duration ● DateTime ● ByteData ● UuidValue ● List ● Map # enumの定義例 enum: Animal serialized: byName values: - dog - cat - bird # classの定義例 class: User fields: name: String favoriteAnimal: List createdAt: DateTime

Slide 20

Slide 20 text

Serverpodの主な機能 ● コードの自動生成 ● ORMとリレーション ● DBマイグレーション ● 認証 ● ファイルアップロード ● サーバーの可視化 ● データストリーミング ● キャッシュ ● タスクスケジューリング ● ヘルスチェック ● 簡単なデプロイ ● ビルトインのWEBサーバー

Slide 21

Slide 21 text

ORMとリレーション モデルにtableキーとテーブル名を追加することでORMが自動生成される。 class: User table: user # 追加 fields: ... // データの作成 User.db.insertRow(session, user); // データの取得 User.db.findById(session, userId); // データの更新 User.db.updateRow(session, newUser); // データの削除 User.db.deleteRow(session, user); // 自動生成されたUserモデル abstract class User extends _i1.TableRow { ... // DBアクセスするためのリポジトリ static const db = UserRepository._(); ... } $ serverpod generate 複数のデータもCRUD操作も可能

Slide 22

Slide 22 text

ORMとリレーション relationキーワードを設定することでリレーションが設定できる。 データへのアクセスを簡略化するために、オブジェクトを指定してリレー ションを設定することが可能。 class: User table: user fields: # IDのリレーションを指定する場合 addressId: int, relation(parent=address) # オブジェクトのリレーションを指定する場合 address: Address?, relation class: Address table: address fields: street: String

Slide 23

Slide 23 text

オブジェクトのリレーションを指定することで一度でリレーションしたデー タの取得が可能になる。 一度で取得するには includeオプションを指定する ORMとリレーション Future getUser(Session session, int userId) async { // Addressオブジェクトを取得し、 Userモデルにセットして返す // イメージ: {...略..., "addressId":1,"address":{"id":1,"street":"hoge city"}} return await User.db.findById(session, userId, include: User.include(address: Address.include())); }

Slide 24

Slide 24 text

ORMとDBマイグレーション トランザクションやページネーションの実装も可能。 // トランザクション: Addressオブジェクトを取得し、 Userモデルにセットして返す return await session.dbNext.transaction((transaction) async { await Address.db.insertRow(session, address, transaction: transaction); return await User.db.insertRow(session, user, transaction: transaction); }); // ページネーション: limit: 1回で取得できる最大レコード数 , offest: レコードを取得する開始点 final users = await User.db.find( session, limit: 10, orderBy: (t) => t.name, // 名前順 offset: 30, );

Slide 25

Slide 25 text

Serverpodの主な機能 ● コードの自動生成 ● ORMとリレーション ● DBマイグレーション ● 認証 ● ファイルアップロード ● サーバーの可視化 ● データストリーミング ● キャッシュ ● タスクスケジューリング ● ヘルスチェック ● 簡単なデプロイ ● ビルトインのWEBサーバー

Slide 26

Slide 26 text

DBマイグレーション 作成したモデルからマイグレーションのファイルが生成できる。 $ serverpod create-migration を実行 以下のフラグを指定して実行することでマイグレーションを適用ができる。 migrationsフォルダが⽣成される $ dart run bin/main.dart --apply-migrations 個別でマイグレーションの対象にしないように 設定することも可能 マイグレーションだけ適応することも可能

Slide 27

Slide 27 text

Serverpodの主な機能 ● コードの自動生成 ● ORMとリレーション ● DBマイグレーション ● 認証 ● ファイルアップロード ● サーバーの可視化 ● データストリーミング ● キャッシュ ● タスクスケジューリング ● ヘルスチェック ● 簡単なデプロイ ● ビルトインのWEBサーバー

Slide 28

Slide 28 text

Serverpodでは以下の認証が簡単に実装できる。 ● Eメール ● Google ● Apple ● Firebase 認証 Firebase経由でのGoogle認証の実装例について説明します。

Slide 29

Slide 29 text

認証(サーバー側の実装) 1. serverpod_auth_server モジュールのインストール 2. マイグレーションの作成 3. Firebaseの秘密鍵ファイルを配置 server/config/firebase_service_account_key.json に配置 4. マイグレーションを適用してサーバーを実行 $ serverpod create-migration 認証に使⽤するテーブル等を⽣成する $ dart pub add serverpod_auth_server Firebaseプロジェクトの作成・設定は 割愛いたします 🙇 ※ .gitignoreに記載がないので注意 $ dart run bin/main.dart --apply-migrations

Slide 30

Slide 30 text

認証(クライアント・アプリ側の実装) 1. 各モジュールのインストール ● クライアント: serverpod_auth_client ● アプリ ○ Serverpod認証 : serverpod_auth_shared_flutter / serverpod_auth_firebase_flutter ○ Firebase認証: google_sign_in / firebase_core / firebase_auth 2. Firebaseの設定 flutterfire_cli等で設定を実施 アプリ側の設定の詳細は割愛いたします🙇

Slide 31

Slide 31 text

認証(クライアント・アプリ側の実装) 3. ClientとSessionManagerの作成 ● SessionManager : ユーザーの認証情報を管理する役割 client = Client( 'http://$localhost:8080/', authenticationKeyManager: FlutterAuthenticationKeyManager(), )..connectivityMonitor = FlutterConnectivityMonitor(); sessionManager = SessionManager(caller: client.modules.auth); await sessionManager.initialize();

Slide 32

Slide 32 text

認証(クライアント・アプリ側の実装) 4. Googleでのサインインの実装 // Google認証後にFirebaseに登録する final googleUser = await GoogleSignIn().signIn(); final googleAuth = await googleUser!.authentication; final googleAuthCredential = GoogleAuthProvider.credential( accessToken: googleAuth.accessToken, idToken: googleAuth.idToken); final credential = await FirebaseAuth.instance.signInWithCredential(googleAuthCredential); // FirebaseのidTokenを取得する(googleAuthのidTokenではない!) final idToken = await credential.user!.getIdToken(); // Serverpodのauthモジュールを使ってサーバー認証 final response = await client.modules.auth.firebase.authenticate(idToken!); if (response.success) { // 認証成功の場合、セッションマネージャーにユーザー情報を登録する await sessionManager.registerSignedInUser(response.userInfo!, response.keyId!, response.key!); }

Slide 33

Slide 33 text

認証 認証した後はセッションマネージャーやセッションからユーザーID等が取得 できる。 // アプリ側 final user = sessionManager.signedInUser; // サーバー側 Future hello(Session session, String name) async { final userId = await session.auth.authenticatedUserId; return 'Hello $name, user id is $userId'; }

Slide 34

Slide 34 text

Serverpodの主な機能 ● コードの自動生成 ● ORMとリレーション ● DBマイグレーション ● 認証 ● ファイルアップロード ● サーバーの可視化 ● データストリーミング ● キャッシュ ● タスクスケジューリング ● ヘルスチェック ● 簡単なデプロイ ● ビルトインのWEBサーバー

Slide 35

Slide 35 text

ファイルアップロード ファイルアップロードするためのモジュールがサポートされている。 デフォルトの機能としてデータベースにファイルバイナリの保存をすることができるが、クラウドスト レージを使用するように推奨されている。 ● Google Cloud Storage ○ serverpod_cloud_storage_gcp ● Amazon S3 ○ serverpod_cloud_storage_s3

Slide 36

Slide 36 text

ローカル環境のストレージにファイルを保存する場合は、ファイル操作する ためのインターフェースを実装する必要がある。 やりたかったこと ● ローカルにS3互換のMinIOコンテナを起動 ● ServerpodからローカルのMinIOに接続し操作する ○ 提供されているモジュールのホスト名が以下のように指定されており、ローカルホスト を設定できなさそうだった 'https://$bucket.s3-$region.amazonaws.com' ファイルアップロード

Slide 37

Slide 37 text

Serverpodの主な機能 ● コードの自動生成 ● ORMとリレーション ● DBマイグレーション ● 認証 ● ファイルアップロード ● サーバーの可視化 ● データストリーミング ● キャッシュ ● タスクスケジューリング ● ヘルスチェック ● 簡単なデプロイ ● ビルトインのWEBサーバー

Slide 38

Slide 38 text

サーバーの可視化 Serverpod insight アプリケーションを使っ て、サーバーの状態が確認できる。 ● リクエストの詳細 ○ どのエンドポイントがコールされたか ○ リクエスト日時 ○ 応答速度 など ● サーバーのヘルスチェック ○ CPU/メモリの使用率など サーバー側にログ出⼒を追加することで、 詳細なログの監視も可能になる

Slide 39

Slide 39 text

まとめ

Slide 40

Slide 40 text

まとめ ● Serverpodを使用することでサーバーサイドからクライアントアプリま でフルスタックにDartで開発ができる ● 提供されたモジュールやコードの自動生成によってサーバー側のコード を少なく機能実装ができる ● サーバー・アプリ間でのモデルが共通化されているため一貫性を保つこ とができる

Slide 41

Slide 41 text

ご清聴ありがとうございました