Simple CRUD - シナリオ編
本チュートリアルは、単一のデータモデルを定義し、データモデルに対してCRUDするためのAPIを作成します。
Model
でデータベーススキーマを定義生成して、生成されたデータモデルをScenario
からCRUD操作するアプリケーションを作成しましょう。
Step1. データモデルを作成する
データモデルは、jsonschemaによって定義します。 ここでは、Employment
という名前のデータモデルを作成しています。
category: Tutorial
scenario_auto_generation_mode: false
model:
name: Employment
schema:
type: object
properties:
entryNumber:
type: string
firstName:
type: string
lastName:
type: string
email:
type: string
salaryRequirements:
type: integer
required:
- entryNumber
- firstName
- lastName
- email
constraints:
primary_key: entryNumber
unique_keys:
- email
データモデルを登録したら、REPLでモデルオブジェクトやデータベーステーブルが生成されていることを確認しましょう。
>>> models↵
Tutorial Employment
>>> desc Employment;↵
| Field | Type | Null | Key | Default | Extra |
entryNumber varchar(255) NO PRI NULL
firstName varchar(255) NO NULL
lastName varchar(255) NO NULL
email varchar(255) NO UNI NULL
salaryRequirements int YES NULL
>>>
Step2. カウンタを作成する
Employment
の主キーは、entryNumber
です。新規登録時は、entryNumber
を払い出す必要があります。ここでは、entryNumber
は、E + 数字4桁
の文字列とします。
Counter
サービスを利用してユニークなentryNumber
を払い出せるようにしましょう。
カウンタの定義は以下のようになります。
counter_name: entryNumber
counter_type: number
counter_format: E$
counter_script: null
max_num: 9999
min_num: 1
padding: true
カウンタを作成したら、entryNumber
が払い出せることを確認しましょう。
>>> n = await Counter.allocate("entryNumber")↵
... print(n)↵
... ↵
↵
E0001
>>>
Step3. 新規登録シナリオを作成する
それでは、新規登録シナリオを作成しましょう。 以下の定義でシナリオを作成してください。
category: Tutorial
name: createEmployment
uri: /tutorlals/employments
method: POST
routing_auto_generation_mode: true
spec:
response:
normal:
codes:
- 200
request:
headers:
type: object
properties:
Content-Type:
type: string
default: application/json
required:
- Content-Type
body:
type: object
properties:
firstName:
type: string
pattern: '[a-zA-Z]'
lastName:
type: string
pattern: '[a-zA-Z]'
email:
type: string
format: email
salaryRequirements:
type: integer
minimum: 0
maximum: 99999999
required:
- firstName
- lastName
- email
- salaryRequirements
commands:
- command: script
kwargs:
code: |-
entryNumber = await Counter.allocate("entryNumber")
async with model.aiodb() as conn:
cursor = await conn.execute(model.Employment.select().where(model.Employment.c.email==context.request.body.email))
if cursor.rowcount != 0:
raise Error(400, reason="E-mail address is already registered")
await conn.execute(model.Employment.insert().values(entryNumber=entryNumber,
firstName=context.request.body.firstName,
lastName=context.request.body.lastName,
email=context.request.body.email,
salaryRequirements=context.request.body.salaryRequirements))
context.session.finish(dict(entryNumber=entryNumber))
request_timeout: 60
connect_timeout: 60
このシナリオを保存すると、POST /tutorlals/employments
で待ち受けるAPIが生成されます。この例のようにspec
でAPI入力仕様を記述すると、リクエストバリデーションが有効になります。spec
に記述されているスキーマ情報は、API Gatewayに生成されるAPIルーティング情報に保存され、API Gatewayでリクエストが受信されたときにバリデーションされます。バリデーションがNGの場合、400 BadRequest
を返却します。シナリオ側でもリクエスト受信時にバリデーションする場合は、request_validation
コマンドを使用してください。
シナリオの動作としては、最初にカウンタサービスを利用してentryNumber
を払い出します。
次にデータベースに接続して、email
が重複しているデータが存在しないかをチェックします。重複している場合は、400 BadRequest
を返却します。
重複がない場合は、データを登録します。最後にentryNumber
を200 Success
応答します。
シナリオを作成したら、Scenario.run
で実行してみましょう。実行前後でEmployment
テーブルのレコードを検索して状況を確認してみてください。
尚、Scenario
組込みオブジェクトの詳細は、Docs » リファレンス » ビルトインオブジェクト
を参照してください。
>>> select * from Employment;↵
Empty set
>>> r = await Scenario.run("createEmployment", firstName="Ray", lastName="Amuro", email="amuroray@uc.com", salaryRequirements=10000000)↵
... print(r.body)↵
... ↵
↵
b'{"entryNumber":"E0003"}'
>>> select * from Employment;↵
| entryNumber | firstName | lastName | email | salaryRequirements |
E0003 Ray Amuro amuroray@uc.com 10000000
>>>
Step4. 検索シナリオを作成する
検索シナリオを作成しましょう。以下の定義でシナリオを作成してください。
category: Tutorial
name: getEmployment
uri: /tutorials/employments
additional_paths:
- '/tutorials/employments/{entryNumber}'
method: GET
routing_auto_generation_mode: true
spec:
response:
normal:
codes:
- 200
request:
params:
type: object
properties:
entryNumber:
type: array
items:
type: string
firstName:
type: array
items:
type: string
pattern: '[a-zA-Z]'
lastName:
type: array
items:
type: string
pattern: '[a-zA-Z]'
email:
type: array
items:
type: string
format: email
salaryRequirements:
type: array
items:
type: integer
minimum: 0
maximum: 99999999
resources:
type: object
properties:
entryNumber:
type: string
commands:
- command: script
kwargs:
code: |-
async with model.aiodb() as conn:
if context.request.resources.entryNumber:
cursor = await conn.execute(model.Employment.select().where(model.Employment.c.entryNumber==context.request.resources.entryNumber))
if cursor.rowcount == 0:
raise Error(404, reason="Could not found employment")
employment = await cursor.fetchone()
context.session.finish(rowtodict(employment))
return
logger.info(context.request.params.dictionary)
cursor = await conn.execute(model.Employment.select().where(where_statement(model.Employment, context.request.params.dictionary)))
employments = await cursor.fetchall()
context.session.finish([rowtodict(employment) for employment in employments])
request_timeout: 60
connect_timeout: 60
このAPIは、GET /tutorials/employments
を介してアクセスするとEmployment
リストを返します。GET /tutorials/employments/{entryNumber}
でアクセスすると、対応するEmployment
が返されます。GET /tutorials/employee?lastName=Amuro
のようなクエリ検索もできます。クエリ検索に対応するためにSQLのWHERE句を組み立てる必要がありますが、この例ではwhere_statement
組込み関数にクエリパラメータの辞書を渡して自動生成しています。
シナリオを作成したら、Scenario.run
で実行してみましょう。
>>> r = await Scenario.run("getEmployment")↵
... print(json.dumps(r.body, indent=4))↵
... ↵
[
{
"entryNumber": "E0011",
"firstName": "Ray",
"lastName": "Amuro",
"email": "amuroray@uc.com",
"salaryRequirements": 10000000
}
]↵
↵
>>> r = await Scenario.run("getEmployment", lastName="Amuro")↵
... print(json.dumps(r.body, indent=4))↵
... ↵
[
{
"entryNumber": "E0011",
"firstName": "Ray",
"lastName": "Amuro",
"email": "amuroray@uc.com",
"salaryRequirements": 10000000
}
]↵
↵
>>> r = await Scenario.run("getEmployment", lastName="Hoge")↵
... print(r.body)↵
... ↵
[]↵
↵
>>> r = await Scenario.run("getEmployment", entryNumber="E0001")↵
... print(r.body)↵
... ↵
{'errorCode': 404, 'errorMessage': 'Could not found employment', 'moreInfo': None}↵
↵
>>> r = await Scenario.run("getEmployment", entryNumber="E0011")↵
... print(json.dumps(r.body, indent=4))↵
... ↵
{
"entryNumber": "E0011",
"firstName": "Ray",
"lastName": "Amuro",
"email": "amuroray@uc.com",
"salaryRequirements": 10000000
}↵
↵
Step5. 更新シナリオを作成する
更新シナリオを作成しましょう。以下の定義でシナリオを作成してください。
category: Tutorial
name: updateEmployment
uri: '/tutorials/employments/{entryNumber}'
method: PUT
routing_auto_generation_mode: true
request_timeout: 60
connect_timeout: 60
spec:
response:
normal:
codes:
- 200
request:
headers:
type: object
properties:
Content-Type:
type: string
default: application/json
required:
- Content-Type
body:
type: object
properties:
firstName:
type: string
pattern: '[a-zA-Z]'
lastName:
type: string
pattern: '[a-zA-Z]'
email:
type: string
format: email
salaryRequirements:
type: integer
minimum: 0
maximum: 99999999
resources:
type: object
properties:
entryNumber:
type: string
required:
- entryNumber
commands:
- command: script
kwargs:
code: |-
async with model.aiodb() as conn:
if not context.request.resources.entryNumber:
raise Error(400, reason="entryNumber is not specified")
cursor = await conn.execute(model.Employment.select().where(model.Employment.c.entryNumber==context.request.resources.entryNumber))
if cursor.rowcount == 0:
raise Error(404, reason="Could not found employment")
row = await cursor.fetchone()
employment = rowtodict(row)
employment.update(context.request.body.dictionary)
await conn.execute(model.Employment.update().where(model.Employment.c.entryNumber==context.request.resources.entryNumber).values(**employment))
context.session.finish({"entryNumber": context.request.resources.entryNumber})
シナリオを作成したら、Scenario.run
で実行してみましょう。
>>> r = await Scenario.run("updateEmployment", entryNumber="E0011", email="updated@gmail.com")↵
... print(json.dumps(r.body, indent=4))↵
... r = await Scenario.run("getEmployment")↵
... print(json.dumps(r.body, indent=4))↵
... ↵
{
"entryNumber": "E0011"
}↵
↵
[
{
"entryNumber": "E0011",
"firstName": "Ray",
"lastName": "Amuro",
"email": "updated@gmail.com",
"salaryRequirements": 10000000
}
]↵
↵
Step6. 削除シナリオを作成する
削除シナリオを作成しましょう。以下の定義でシナリオを作成してください。
category: Tutorial
name: deleteEmployment
uri: '/tutorials/employments/{entryNumber}'
method: DELETE
routing_auto_generation_mode: true
spec:
response:
normal:
codes:
- 200
request:
resources:
type: object
properties:
entryNumber:
type: string
required:
- entryNumber
commands:
- command: script
kwargs:
code: |-
async with model.aiodb() as conn:
if not context.request.resources.entryNumber:
raise Error(400, reason="entryNumber is not specified")
cursor = await conn.execute(model.Employment.select().where(model.Employment.c.entryNumber==context.request.resources.entryNumber))
if cursor.rowcount == 0:
raise Error(404, reason="Could not found employment")
await conn.execute(model.Employment.delete().where(model.Employment.c.entryNumber==context.request.resources.entryNumber))
context.session.set_status(204)
context.session.finish()
request_timeout: 60
connect_timeout: 60
シナリオを作成したら、Scenario.run
で実行してみましょう。
>>> r = await Scenario.run("deleteEmployment", entryNumber="E0012")↵
... print(r.code)↵
... ↵
204↵
↵
>>> r = await Scenario.run("getEmployment")↵
... print(json.dumps(r.body, indent=4))↵
... ↵
[]↵
以上でSimple CRUD - シナリオ編
のチュートリアルは完了です。ここでは作成したシナリオの動作確認はREPL上で行いましたが時間に余裕のある方は、curl
コマンドやcallout
組込みオブジェクトなどHTTPクライアントを用いて、API Gateway経由でAPIを呼び出してみてください。