1. 概要

ScalaでシンプルなCRUDスタイルのRESTAPIを構築するには、PlayFrameworkが優れたソリューションです。 複雑でないAPIを備えているため、あまり多くのコードを記述する必要はありません。

このチュートリアルでは、Playを使用してScalaでRESTAPIを構築します。 データ形式としてJSONを使用し、複数のHTTPメソッドとステータスコードを確認します。

2. サンプルプロジェクト

2.1. 私たちは何を構築していますか?

例として、todoリストアプリケーションを作成します。 データベースを使用するのではなく、todoリストアイテムをメモリに保存します。

アプリケーションはいくつかのエンドポイントを提供し、小さな段階的なステップで構築します。

また、アプリケーションを拡張しながら実行およびテストする方法についても説明します。

2.1. プロジェクトを設定する

まず、sbtテンプレートを使用して新しいPlayFrameworkプロジェクトを設定しましょう。

$ sbt new playframework/play-scala-seed.g8

これにより、1つのコントローラー( app / controllers ディレクトリー内)、2つのHTMLファイル( app / views ディレクトリー内)、および基本構成(内)を持つ新しいプロジェクトが作成されます。 ] conf ディレクトリ)。

それらは必要ないので、 HomeController.scala index.scala.html 、およびmain.scala.htmlファイルを削除しましょう。 routersファイルの既存のコンテンツも削除しましょう。

3. 最初のRESTエンドポイント

NoContent応答を返すエンドポイントを実装することから始めましょう。

3.1. コントローラを作成する

まず、新しいコントローラークラス app /controllersディレクトリに作成します。

@Singleton
class TodoListController @Inject()(val controllerComponents: ControllerComponents)
extends BaseController {
}

新しいクラスはBaseControllerを拡張し、それと互換性のあるコンストラクターを備えています。

@Inject アノテーションを使用して、必要なクラスの依存関係を自動的に渡すようにPlayFrameworkに指示しました。 また、フレームワークが1つのインスタンスのみを作成するように、クラスを@Singletonとしてマークしました。 これは、すべてのリクエストで再利用されることを意味します。

3.2. Scalaでリクエストを処理する

これでコントローラーができました。サーバーがRESTリクエストを受信したときに呼び出されるメソッドを作成しましょう。 まず、Play Framework Actionを返すgetAll関数を定義します。

def getAll(): Action[AnyContent] = Action {
  NoContent
}

Action は、リクエストパラメータへのアクセスを提供し、HTTPレスポンスを返すことができます。 この場合、NoContentステータスを返すだけです。

3.3. ルートにエンドポイントを追加する

次に、コントローラーをルートファイルに追加する必要があります。

GET     /todo                       controllers.TodoListController.getAll

処理するHTTPメソッド、パス、およびリクエストを処理するScalaメソッドの正規名を指定する必要があります。 それらを空白で区切る必要があります。

慣例により、ファイルを読みやすくするために、パラメーターを簡単に区別できる列に保持します。

3.4. RESTエンドポイントのテスト

これでエンドポイントができました。アプリケーションを起動しましょう

$ sbt run

しばらくすると、ログメッセージが表示されます。

[info] p.c.s.AkkaHttpServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000

これは、アプリケーションが要求を処理する準備ができていることを意味します。

これで、curlを使用してAPIをテストできます。curlの冗長モードを使用してみましょう。

$ curl -v localhost:9000/todo

GET /todo HTTP/1.1
Host: localhost:9000
User-Agent: curl/7.64.1
Accept: */*

HTTP/1.1 204 No Content.

これで、動作するPlay Frameworkアプリケーションができました。それに、いくつかの機能を追加しましょう。

4. 返品アイテム

次に、ToDoリスト全体を返します。 データモデルを定義し、タスクのメモリ内コレクションを作成し、TodoListControllerを変更してJSONオブジェクトを返しましょう。

4.1. モデルを定義する

まず、 app/modelsディレクトリに新しいクラスを作成します。

case class TodoListItem(id: Long, description: String, isItDone: Boolean)

4.2. インメモリリスト

ここで、TodoListControllerクラスでタスクのリストを定義します。 このリストは変更されるため、mutableコレクションパッケージが必要です。

class TodoListController @Inject()(val controllerComponents: ControllerComponents)
extends BaseController {
  private val todoList = new mutable.ListBuffer[TodoListItem]()
  todoList += TodoListItem(1, "test", true)
  todoList += TodoListItem(2, "some other value", false)

テストを支援するために、起動時に使用できるように、このリストにハードコードされた値をいくつか追加しました。

4.3. JSONフォーマッター

次に、TodoListItemオブジェクトをJSONに変換するJSONフォーマッターを作成しましょう。 まず、JSONライブラリをインポートします。

import play.api.libs.json._

その後、TodoListControllerクラス内にJSONフォーマッターを作成します。

implicit val todoListJson = Json.format[TodoListItem]

常にJson.toJson関数に渡さなくても済むように、暗黙のフィールドにします。

4.4. getAll関数の条件付きロジック

これで、返すデータがいくつかあります。 getAll関数を変更して、リストが空の場合にのみNoContentステータスコードを返すようにします。 それ以外の場合は、JSONに変換されたリストアイテムを返す必要があります。

def getAll(): Action[AnyContent] = Action {
  if (todoList.isEmpty) {
    NoContent
  } else {
    Ok(Json.toJson(todoList))
  }
}

4.5. テスト

GETエンドポイントを再テストしてみましょう。

$ curl localhost:9000/todo

[
  {
    "id": 1,
    "description": "test",
    "isItDone": true
  },
  {
    "id": 2,
    "description": "some other value",
    "isItDone": false
  }
]

5. 1つのアイテムを返す

REST APIは、パスパラメーターを介した個々のアイテムの取得もサポートする必要があります。 次のようなことができるようにしたいと考えています。

$ curl localhost:9000/todo/1

これにより、アイテムが返されます。IDが不明な場合は、NotFoundが返されます。 それを実装しましょう。

5.1. ルートへのパラメータの追加

まず、routersファイルで新しいエンドポイントを定義します。

GET    /todo/:itemId           controllers.TodoListController.getById(itemId: Long)

表記 /todo/:itemId Play Frameworkは、 /todo/ プレフィックスを付けて、 itemId 変数。 その後、Playは getById 関数とパス itemId その最初のパラメータとして。

パラメータタイプを指定したため、テキストが自動的に数値に変換されるか、パラメータが数値でない場合はBadRequestが返されます。

5.2. 一致する要素を見つける

TodoListController、に、getByIdメソッドを追加しましょう。

def getById(itemId: Long) = Action {
  val foundItem = todoList.find(_.id == itemId)
  foundItem match {
    case Some(item) => Ok(Json.toJson(item))
    case None => NotFound
  }
}

このメソッドは、 itemId パラメーターを取得し、同じIDを持つtodoリストアイテムを見つけようとします。

find 関数を使用しています。この関数は、Optionクラスのインスタンスを返します。 したがって、パターンマッチングを使用して、空のOptionと値を持つOptionを区別します。

アイテムがtodoリストに存在する場合、OK応答で返されるJSONに変換されます。 それ以外の場合は、 NotFound が返され、HTTP404が発生します。

5.3. テスト

これで、見つかったアイテムの取得を試みることができます。

$ curl localhost:9000/todo/1

{
  "id": 1,
  "description": "test",
  "isItDone": true
}

または、そうでないアイテム:

$ curl -v localhost:9000/todo/999

HTTP/1.1 204 No Content.

6. PUTおよびDELETEメソッド

Play FrameworkはすべてのHTTPメソッドを処理するため、PUTまたはDELETEを使用する場合は、routersファイルでそれらを構成する必要があります。 たとえば、アイテムを完了としてマークし、完了したアイテムを削除するエンドポイントを定義できます。

PUT     /todo/done/:itemId    controllers.TodoListController.markAsDone(itemId: Long)
DELETE  /todo/done            controllers.TodoListController.deleteAllDone

7. 新しいタスクの追加

最後に、新しいアイテムを含むPOSTリクエスト送信するときに、実装で新しいタスクを追加する必要があります。

$ curl -v -d '{"description": "some new item"}' -H 'Content-Type: application/json' -X POST localhost:9000/todo

説明のみを指定する必要があることに注意してください。 このアプリケーションは、idと初期ステータスを生成します。

7.1. routesファイルのPOSTエンドポイント

まず、routersファイルで新しいエンドポイントを指定しましょう。

POST     /todo                      controllers.TodoListController.addNewItem

7.2. データ転送オブジェクト

次に、 app /modelsディレクトリに新しいクラスを追加する必要があります。 description フィールドを含む新しいデータ転送オブジェクト(DTO)を作成します。

case class NewTodoListItem(description: String)

7.3. JSONオブジェクトの読み取り

TodoListController では、その新しいクラスにJSONフォーマッターが必要です。

implicit val newTodoListJson = Json.format[NewTodoListItem]

また、JSON入力からNewTodoListItemオブジェクトを作成するメソッドを定義しましょう。

def addNewItem() = Action { implicit request => 
  val content = request.body 
  val jsonObject = content.asJson 
  val todoListItem: Option[NewTodoListItem] = 
    jsonObject.flatMap( 
      Json.fromJson[NewTodoListItem](_).asOpt 
    )
}

私たちの方法では、 content.asJson 指定されたJSONオブジェクトを解析し、 オプション。 デシリアライズが成功した場合にのみ、有効なオブジェクトを取得します。

NewTodoListItemまたはContent-Typeapplication/ json ではなかったため、発信者から逆シリアル化できないコンテンツが送信された場合、になります。代わりになし

7.4. 新しいアイテムの追加

それでは、addNewItemの最後に次のコードを追加しましょう。 これにより、新しいオブジェクトが保存されてHTTP Created が返されるか、BadRequestで応答します。

def addNewItem() = Action { implicit request =>
  // existing code

  todoListItem match {
    case Some(newItem) =>
      val nextId = todoList.map(_.id).max + 1
      val toBeAdded = TodoListItem(nextId, newItem.description, false)
      todoList += toBeAdded
      Created(Json.toJson(toBeAdded))
    case None =>
      BadRequest
  }
}

7.5. テスト

リストに新しいアイテムを追加してテストしてみましょう。

$ curl -v -d '{"description": "some new item"}' -H 'Content-Type: application/json' -X POST localhost:9000/todo

HTTP/1.1 201 Created.
{
  "id": 3,
  "description": "some new item",
  "isItDone": false
}

応答として、Createdステータスコードと新しいアイテムが応答本文に表示されます。 さらに、アイテムがリストに追加されたことを確認するために、すべてのアイテムを再度取得できます。

$ curl localhost:9000/todo

[
  {
    "id": 1,
    "description": "test",
    "isItDone": true
  },
  {
    "id": 2,
    "description": "some other value",
    "isItDone": false
  },
  {
    "id": 3,
    "description": "some new item",
    "isItDone": false
  }
]

今回は、JSON配列に新しく追加されたオブジェクトを含む3つのオブジェクトが含まれている必要があります。

Content-Typeヘッダーを指定することが重要であることに注意する必要があります。 それ以外の場合、PlayFrameworkはデータを次のように読み取ります application/x-www-form-urlencoded そしてそれをJSONオブジェクトに変換できません。

8. 結論

この記事では、Scalaを使用してPlayFrameworkにRESTAPIを実装しました。

まず、プロジェクトを初期化し、最初のルートとコントローラークラスを定義しました。 次に、DTOオブジェクトを定義し、JSON形式に変換しました。

また、 curl を使用して、コードが正しく機能することを確認する方法についても説明しました。

サンプルソースコードは、GitHubから入手できます。