Aqueduct事始め

# はじめに
[Dartを触り始めた](https://minase-program.hatenablog.com/entry/2019/08/01/223026?_ga=2.194215145.579923503.1564613559-1961159496.1467993109)今日このごろ。
Dartをサーバサイドで使いたかったのでやってみた。
調べた感じ[aqueduct](https://aqueduct.io/docs/)っていうFWが有名っぽかったのでやってみる。

# TL;DT.
[ソースコード](https://github.com/Tetsuya-Minase/aqueduct-tutorial)

# 前提条件
Dartのインストールが終わっていること。

# インストール
``` bash
$ pub global activate aqueduct
```
# テンプレート作成
```bash
$ aqueduct create aqueduct_tutorial
$ cd aqueduct_tutorial
```
# とりあえず起動してみる
```bash
$ aqueduct serve
> -- Aqueduct CLI Version: 3.2.1
> -- Aqueduct project version: 3.2.1
> -- Preparing...
> -- Starting application 'aqueduct_tutorial/aqueduct_tutorial'
>     Channel: AqueductTutorialChannel
>     Config: /Users/Tetsuya/Document/Program/aqueduct_tutorial/config.yaml
>     Port: 8888
> [INFO] aqueduct: Server aqueduct/1 started.
> [INFO] aqueduct: Server aqueduct/2 started.
  
$ curl localhost:8888/example | jq .
> {
>   "key": "value"
> }
```

# Initialization
Aqueductのアプリケーションは`ApplicationChannel`から始まる。
アプリケーションに1つサブクラス化し、ルーティングやDBへの接続の初期化を行う。
生成されるやつは下記の通り。(コメントだらけなのでそこは削除。)

```dart
import 'aqueduct_tutorial.dart';
  
class AqueductTutorialChannel extends ApplicationChannel {
  // サーバの初期化を行う
  @override
  Future prepare() async {
    logger.onRecord.listen((rec) => print("$rec ${rec.error ?? ""} ${rec.stackTrace ?? ""}"));
  }
  
  // Routingの設定を行う
  @override
  Controller get entryPoint {
    final router = Router();
  
    router
      .route("/example")
      .linkFunction((request) async {
        return Response.ok({"key": "value"});
      });
  
    return router;
  }
}
```

`route`の指定は他にも下記のようにしてワイルドカードなりの指定ができる。

```dart
// projects/1, projects/2などにマッチする
router
    .route("/projects/[:id]")
    .link(() => ProjectController());
  
// `/file/`から始まる全パスにマッチする
router
    .route("/file/*")
    .link(() => FileController());
  
// /healthにマッチする
router
    .route("/health")
    .linkFunction((req) async => Response.ok(null));
```

# Controllers
Controllerはリクエストをハンドリングするところ。
Controllerはorverrideされた`handle`メソッドでリクエストをハンドリングしてくれる。

```dart
class ProjectController extends Controller {
  @override
  FutureOr<RequestOrResponse> handle(Request request) {
    if (request.raw.headers.value("x-secret-key") == "secret!") {
      return request;
    }
  
    return Response.badRequest();
  }
}
```

このメソッドでは`Request`と`Response`を返すことができる。
`Response`を返すとその名の通り、Clientにresponseが返る。
`Request`を返すと後続？のControllerへと渡っていく。
expressで言うとこの`next()`みたいな挙動っぽい。
ControllerからControllerに渡すのは下記みたいにすればできる。

```dart
router
    .route("/file/*")
    // ここの`return request`とすれば後続のControllerが呼ばれる
    .link(() => SelfFileController())
    .link(() => NextFileController());

class SelfFileController extends Controller {
  @override
  FutureOr<RequestOrResponse> handle(Request request) {
      print('selfFileController');
      return request;
  }
}
```

## ResourceControllers

ResourceControllerは最もよく使われるControllerとのこと。
Postで`/project`, Getで`/project`など1つのクラスで複数のリクエストを受けたい時は多々あるはず。
アノテーションでよしなにやってくれる。
これは`Spring`なり`nestjs`なりでみるよくあるやつだと思う。

```dart
// router
router
    .route("/operation")
    .link(() => RestController());
router
    .route("/operation/:id")
    .link(() => RestController());

// Controller
class RestController extends ResourceController {
  @Operation.get('id')
  Future<Response> getProjectById(@Bind.path("id") int id) async {
    // GET /operation/:id
    return Response.ok(id);
  }
  
  @Operation.post()
  Future<Response> createProject(@Bind.body() Object body) async {
    // POST /operation
    return Response.ok(body);
  }
  
  @Operation.get()
  Future<Response> getAllProjects({@Bind.query("limit") int limit: 10}) async {
    // GET /operation
    return Response.ok({'result': [1,2,3]});
  }
}
```

# 叩いてみる
```bash
$ curl localhost:8888/operation
> {"result":[1,2,3]}
$ curl localhost:8888/operation/1
> 1
$ curl -XPOST -H "Content-Type: application/json" localhost:8888/operation -d '{"test": "test"}'
> {"test":"test"}
```

## ManagedObjectControllers
ManagedObjectController<T>はRestのオペレーションを自動的にDatabaseのクエリにマッピングしてくれるResourceController。
例えばPOSTは行を足してくれるし、GETはSELECTしてくれる。
ResourceControllerと違ってControllerを用意しなくても大丈夫。
ただ、用意してカスタマイズをすることもできる。

```dart
router
  .route("/users/[:id]")
  .link(() => ManagedObjectController<Project>(context));
```

# Configuration
Applicationの設定はYAMLファイルに書くことができる。

```yaml
database:
  host: api.projects.com
  port: 5432
  databaseName: project
port: 8000
```

読み込む時はYAMLファイルのキーを指定すればOK。

```dart
class TodoConfig extends Configuration {
  TodoConfig(String path) : super.fromFile(File(path));
  
  DatabaseConfiguration database;
  int port;
}
```

Configを使う時は下記のようにする。
デフォルトではYAMLファイル名は`config.yaml`になっている。

```dart
// 読み込むYAMLファイルまでのパスを渡して上げれば良い
TodoConfig config = TodoConfig(options.configurationFilePath);
```

## Running and Concurrency
Aqueductのアプリケーションは`aqueduct serve`で実行することができる。
Aqueductはマルチスレッドで動かすことができるので、デバッグ用のツール、計測用のツールを付けてアプリケーションを実行するスレッド数を指定して実行できる。

```bash
$ aqueduct serve --observe --isolates 5 --port 8888
```
各スレッドは同じ設定を使いますがそれぞれ別なので、
同じWebサーバーのレプリカを起動するような挙動になるらしい。
このおかげでDBのコネクションプールみたいな動作が暗黙的になるとのこと。

# PostgreSQL ORM
`Query<T>`を使うことでDBのクエリを発行することができる。

下記のような場合だとこれで`SELECT`を実行できる。

```dart
class DbController extends ResourceController {
  DbController(this.context);

  final ManagedContext context;

  @Operation.get()
  Future<Response> getAllProjects() async {
    final query = Query<TutorialProject>(context);

    final results = await query.fetch();

    return Response.ok(results);
  }
}
```
## Defining a Data Model
ORMを使うには、`ManagedObject<T>`を継承したテーブルと同じ構造を持つクラスを作る必要がある。
Javaとかで言う`Entity`だと思う。
下記は`id`,`name`,`dueDate`というフィールドを持つテーブルのサンプル。
privateのクラスを作成し、それをベースに`ManagedObject`を継承したクラスを作成した。

```dart
class TutorialProject extends ManagedObject<_TutorialProject> implements _TutorialProject {
  bool get isPastDue => dueDate.difference(DateTime.now()).inSeconds < 0;
}

class _TutorialProject  {
  @primaryKey
  int id;

  @Column(indexed: true)
  String name;

  DateTime dueDate;
}
```

ManagedObject同士はリレーションを持つことができる。
持てる関係は`has-one`,`has-many`,`many-to-many`の3つ。
関係を指定するときには必ずどちらのクラスにも記載が必要になる。
何を言ってるかわからないと思うので、下記のようにすれば良いということ。

```dart
class Project extends ManagedObject<_Project> implements _Project {}
class _Project {
  ...

  // has-manyの指定
  ManagedSet<Task> tasks;
}

class Task extends ManagedObject<_Task> implements _Task {}
class _Task {
  ...

  // has-manyで持たれる側
  @Relate(#tasks)
  Project project;
}
```

## Database Migrations
aqueductのCLIは、ManagedObjectを変更した際に自動でdatabaseのmigration用Scriptを生成してくれる。
下記のように実行するとScriptを生成するだけでなく、DBの更新もやってくれる。

``` bash
$ aqueduct db generate
$ aqueduct db upgrade --connect postgres://user:password@host:5432/database
```

# OAuth2.0
OAuthにもデフォルトで対応してくれているらしい。
ただ残念なことにOAuthの仕組みを良く分かっていないので、なるほど・・・？って感じ。
一応使い方はまとめる。

アプリケーションのサービスとして`AuthServer`とその`delegate`を作成する。
delegateはトークンの生成方法と保管方法を設定できる。
デフォルトではアクセストークンはランダムな32byteの文字列で、
クライアントID、トークン、アクセスコードはORMを使用してDBに保存される。

```dart
import 'package:aqueduct/aqueduct.dart';
import 'package:aqueduct/managed_auth.dart';

class AppApplicationChannel extends ApplicationChannel {
  AuthServer authServer;
  ManagedContext context;

  @override
  Future prepare() async {
    context = ManagedContext(...);

    final delegate = ManagedAuthDelegate<User>(context);
    authServer = AuthServer(delegate);
  }
}
```

アクセストークンとユーザー資格情報を交換するための組み込み認証Controllerは、
`AuthController`および`AuthCodeController`という名前。

```dart
Controller get entryPoint {
  final router = Router();

  // POST /auth/token with username and password (or access code) to get access token
  router
    .route("/auth/token")
    .link(() => AuthController(authServer));

  // GET /auth/code returns login form, POST /auth/code grants access code
  router
    .route("/auth/code")
    .link(() => AuthCodeController(authServer));

  // ProjectController requires request to include access token
  router
    .route("/projects/[:id]")
    .link(() => Authorizer.bearer(authServer))
    .link(() => ProjectController(context));

  return router;
}
```

aqueductのCLIにはクライアント識別子とアクセス範囲を管理するためのツールがある。
下記のようにすれば良さそう。

```bash
    $ aqueduct auth add-client \
      --id com.app.mobile \
      --secret foobar \
      --redirect-uri https://somewhereoutthere.com \
      --allowed-scopes "users projects admin.readonly"
```

# Logging
全リクエストのログを取ることができる。
下記のようにApplicationChannelにloggerを設定すればOK。

```dart
class WildfireChannel extends ApplicationChannel {
  @override
  Future prepare() async {
    logger.onRecord.listen((record) {
      print("$record");
    });
  }
}
```

# まとめ

今回はaqueductのTourを進めてみた。
まだTourしかやってないけど、悪くはなさそうな感じ。
実際に何かを作ってみてわかることもあると思うので、とりあえず何か作ってみます。
それでは、今回はこの辺で。

# 参考サイト

- [公式サイト](https://aqueduct.io/)
    - [今回やったtour](https://aqueduct.io/docs/tour/)