Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
JSON Schema Centralized Design
Search
pika_shi
November 26, 2017
Technology
5.2k
5
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
JSON Schema Centralized Design
Node Fest 2017
pika_shi
November 26, 2017
More Decks by pika_shi
See All by pika_shi
「規約に同意」のUX -ストレスフリーな同意UIとその実現方法-
pika_shi
21
15k
Other Decks in Technology
See All in Technology
Claude Codeの体系的な理解と知識のフック
oikon48
8
6k
dbt と Snowflake と tag
kevinrobot34
2
290
Kiro5兄弟のいまどきのセキュリティ基礎知識
kentapapa
0
380
VPCでもFloatingIPを使いたいんだ
y_kotani
1
130
「ミスを許さない手順書」を作ってみた 〜 個人的にはこれ以上できることはあまりなさそう/20260827-ssmjp-operation-procedure-update
opelab
13
9k
現場の暗黙知を継承するAIエージェント — 対話から生まれる長期記憶と Skills
atsukish
0
130
PdMをやめて、 "プロダクトビルダー"という 働き方に変えました / PdM to Product Builder
shikichee
0
120
AWSとAzureのマルチクラウド活用における強い味方___AWS_Kiroを使った二刀流スキル作成.pdf
duelist2020jp
1
140
医療の現場を変革に挑戦した半年間の軌跡 - PythonとAIで現場を変える / From Code to Care
soudai
PRO
1
640
[RSJ26] Flow as Flow: Modeling Robot Velocity Fields as Probability Velocity Fields
keio_smilab
PRO
0
110
EMの役割で 変わったこと・変わらなかったこと
sansantech
PRO
1
190
markdown-poster Introduction
kazamori
0
400
Featured
See All Featured
Exploring anti-patterns in Rails
aemeredith
3
480
Pawsitive SEO: Lessons from My Dog (and Many Mistakes) on Thriving as a Consultant in the Age of AI
davidcarrasco
0
220
End of SEO as We Know It (SMX Advanced Version)
ipullrank
3
4.4k
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
2
1.8k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
28
3.6k
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
194
17k
Money Talks: Using Revenue to Get Sh*t Done
nikkihalliwell
0
480
Ruling the World: When Life Gets Gamed
codingconduct
0
310
ReactJS: Keep Simple. Everything can be a component!
pedronauck
666
130k
Building a Scalable Design System with Sketch
lauravandoore
463
34k
Fireside Chat
paigeccino
42
4k
How People are Using Generative and Agentic AI to Supercharge Their Products, Projects, Services and Value Streams Today
helenjbeal
1
290
Transcript
JSON Schema Centralized Design Node Fest Tokyo 2017 (2017/11/26) @pika_shi
- Hikaru Takemura (JSON Schema த৺ઃܭ)
‣ Hikaru Takemura ‣ @pika_shi ‣ FOLIO ‣ Frontend Engineer
(React, Node) ‣ AdriaBlue ‣ Mobile App Developer (SwiA)
API Specifica<on
API Specifica8on ‣ PROS ‣ API ఆٛΛ໌จԽ͓ͯ͘͜͠ͱͰɼϑϩϯτŋόοΫؒͰ ࣮ΛεϜʔζʹਐΊΒΕΔ ‣ body
ͷܕఆٛΛݫີʹ͓͜ͳ͏͜ͱ͕Ͱ͖Δ ‣ ੬ऑੑஅͰͷ URL εΩϟϯͷࡍʹར༻Ͱ͖Δ
API Specifica8on ‣ CONS ‣ ༷ͱ࣮͕ঃʑʹဃ͍ͯ͘͠ ‣ ͦͷ݁Ռɼࢀর͖͢ใ͕͔ΒͣɼMicroservices ؒŋϓϩδΣΫτͰίϛϡχέʔγϣϯʹᴥᴪ͕ੜ͡Δ Service
Service Service Service API Spec
Mo8va8on ‣ ༷ͱ࣮͕ဃ͢ΔͷɼͦΕΒ͕ಠཱʹϝϯςφϯε ͞ΕΔ͔Β ‣ ϝϯςφϯε͢ΔͷΛ 1 ͭʹ͠ɼ༷ŋؚ࣮Ίͯͦ͢ ͷใΛࢀর͢ΔΑ͏ʹ͍ͨ͠ Service
Service Service Service API Spec
JSON Schema & RAML
JSON Schema ‣ JSON Object ͷܕఆٛϑΥʔϚοτ ‣ JSON Ͱهड़ (YAML
Ͱهड़͢Δ߹͕ଟ͍) ‣ minimum, maximum ͔Βɼਖ਼نදݱΛѻ͑Δ paLern ·Ͱɼ༷ʑͳϓϩύςΟ͕ఆٛ͞Ε͍ͯΔ
JSON Schema ‣ JSON Object ͷܕఆٛϑΥʔϚοτ --- $schema: hLp:/ /json-schema.org/draA-04/schema#
id: user type: object required: - id - name - state properUes: id: descripUon: user id type: number name: descripUon: user's name type: string state: descripUon: user state type: number enum: - 1 # acUve - 2 # inacUve addiUonalProperUes: false { "id": 23, "name": “John Due”, "state": 2 } { "id": “23”, "name": “John Due”, "state": 3, “phone”: “+819000000000” } ◦ × user.yml
RAML ‣ REST API ఆٛϑΥʔϚοτ ‣ YAML Ͱهड़ ‣ ༷ΛόʔδϣϯཧͰ͖ɼมߋ
diff ͰཧͰ͖Δ ‣ JSON Schema Λ include Ͱ͖Δ ‣ ڞ௨෦ΛఆٛͰ͖ɼ࠶ར༻͍͢͠ ‣ ଞʹ Swagger, Open API, API Blueprint ͕͋Δ
RAML ‣ REST API ఆٛϑΥʔϚοτ #%RAML 0.8 Utle: User version:
v1.0 schemas: - User: !include user.json # ͖ͬ͞ͷ JSON Schema /user: /{user_id}: uriParameters: user_id: type: number get: descripUon: ֘͢Δ id ͷϢʔβใΛऔಘ responses: 200: descripUon: Ϣʔβͷใ͕औಘͰ͖ͨ߹ body: applicaUon/json: schema: User 404: descripUon: Ϣʔβใ͕ଘࡏ͠ͳ͍߹ user.raml
RAML ‣ REST API ఆٛϑΥʔϚοτ user.raml #%RAML 0.8 Utle: User
version: v1.0 schemas: - User: !include user.json # ͖ͬ͞ͷ JSON Schema /user: /{user_id}: uriParameters: user_id: type: number get: descripUon: ֘͢Δ id ͷϢʔβใΛऔಘ responses: 200: descripUon: Ϣʔβͷใ͕औಘͰ͖ͨ߹ body: applicaUon/json: schema: User 404: descripUon: Ϣʔβใ͕ଘࡏ͠ͳ͍߹
JSON Schema Centralized Design (JSON Schema த৺ઃܭ)
‣ JSON Schema ͱ RAML Λத৺ʹਾ͑ͨΤίγεςϜΛߏங JSON Schema Centralized Design
JSON Schema RAML include API Document URL Λ JS ͷ มͱͯ͠ఆٛ Valida<on FlowType Stub Object
‣ JSON Schema ͱ RAML Λத৺ʹਾ͑ͨΤίγεςϜΛߏங JSON Schema Centralized Design
JSON Schema RAML include API Document URL Λ JS ͷ มͱͯ͠ఆٛ Valida<on FlowType Stub Object ϝϯςφϯε͢Δͷ͜͜ͷΈ
API Document URL Λ JS ͷ มͱͯ͠ఆٛ ① API Document
include JSON Schema RAML Valida<on FlowType Stub Object
① API Document ‣ raml2html Ͱ RAML ͔Β API υΩϡϝϯτΛੜ
‣ γϯϓϧ͔ͭΠϯλϥΫςΟϒͳ HTML υΩϡϝϯτ
‣ raml2html Ͱ RAML ͔Β API υΩϡϝϯτΛੜ ‣ γϯϓϧ͔ͭΠϯλϥΫςΟϒͳ HTML
υΩϡϝϯτ ① API Document JSON Schema
URL Λ JS ͷ มͱͯ͠ఆٛ API Document ② URL Λ
JS ͷมͱͯ͠ఆٛ include JSON Schema RAML Valida<on FlowType Stub Object
② URL Λ JS ͷมͱͯ͠ఆٛ ‣ RAML ͷఆٛΛίʔυͰར༻͠ɼ༷ŋ࣮ؒΛಉظ ‣ RAML
͔Β JS ͷ URL มఆٛϑΝΠϧΛੜ #%RAML 0.8 Utle: User version: v1.0 schemas: - User: !include user.json /users: get: descripUon: ϢʔβҰཡϖʔδ … /user: /{user_id}: uriParameters: user_id: type: number get: descripUon: ֘͢Δ id ͷϢʔβͷϖʔδ … / / @flow type URLType = { [string]: { [string]: string } } export const userUrl: URLType = { page: { /** * ϢʔβҰཡϖʔδ */ users: ‘/users', /** * ֘͢Δ id ͷϢʔβͷϖʔδ */ user: ‘/user/{user_id}' } } user.raml urls.js
‣ RAML ͷఆٛΛίʔυͰར༻͠ɼ༷ŋ࣮ؒΛಉظ urls.js template RAML ② URL Λ JS
ͷมͱͯ͠ఆٛ generator urls.js frontend backend …
‣ RAML ͷఆٛΛίʔυͰར༻͠ɼ༷ŋ࣮ؒΛಉظ RAML ② URL Λ JS ͷมͱͯ͠ఆٛ generator
urls.js frontend backend … #%RAML 0.8 Utle: User version: v1.0 schemas: - User: !include user.json /users: get: descripUon: ϢʔβҰཡϖʔδ displayName: page-users-get … /user: /{user_id}: uriParameters: user_id: type: number get: descripUon: ֘͢Δ id ͷϢʔβͷϖʔδ displayName: page-users-get … user.raml urls.js template
‣ RAML ͷఆٛΛίʔυͰར༻͠ɼ༷ŋ࣮ؒΛಉظ RAML ② URL Λ JS ͷมͱͯ͠ఆٛ generator
urls.js frontend backend … #%RAML 0.8 Utle: User version: v1.0 schemas: - User: !include user.json /users: get: descripUon: ϢʔβҰཡϖʔδ displayName: page-users-get … /user: /{user_id}: uriParameters: user_id: type: number get: descripUon: ֘͢Δ id ͷϢʔβͷϖʔδ displayName: page-users-get … urls.tpl.js ʹ inject ͢ΔͨΊͷ key ͱͯ͠ར༻ user.raml urls.js template
‣ RAML ͷఆٛΛίʔυͰར༻͠ɼ༷ŋ࣮ؒΛಉظ urls.js template RAML ② URL Λ JS
ͷมͱͯ͠ఆٛ generator urls.js frontend backend … / / @flow type URLType = { [string]: { [string]: string } } export const userUrl: URLType = { page: { /** * {{page-users-get.descripUon}} */ users: ‘{{page-users-get.url}}’, /** * {{page-user-user_id-get.descripUon}} */ user: ‘{{page-user-user_id-get.url}}’ } } urls.tpl.js
‣ RAML ͷఆٛΛίʔυͰར༻͠ɼ༷ŋ࣮ؒΛಉظ RAML ② URL Λ JS ͷมͱͯ͠ఆٛ generator
urls.js frontend backend … RAML ͷ displayName (key) ͔Β url descrip<on Λ inject / / @flow type URLType = { [string]: { [string]: string } } export const userUrl: URLType = { page: { /** * {{page-users-get.descripUon}} */ users: ‘{{page-users-get.url}}’, /** * {{page-user-user_id-get.descripUon}} */ user: ‘{{page-user-user_id-get.url}}’ } } urls.tpl.js urls.js template
‣ RAML ͷఆٛΛίʔυͰར༻͠ɼ༷ŋ࣮ؒΛಉظ RAML ② URL Λ JS ͷมͱͯ͠ఆٛ generator
urls.js frontend backend … const fs = require('fs') const ramlParser = require('raml-parser') const handlebars = require('handlebars') const urlMap = {} const setUrlFromRaml = (data, prefix = '') => { data.resources.forEach(resource => { if (resource.methods) { if (urlMap[resource.methods[0].displayName]) { throw new Error('Duplicated displayName') } urlMap[resource.methods[0].displayName] = { url: `${prefix}${resource.relaUveUri}`, descripUon: resource.methods[0].descripUon } } else { setUrlFromRaml(resource, `${prefix}${resource.relaUveUri}`) } }) } ramlParser.loadFile(‘page.raml').then(data => { try { setUrlFromRaml(data) } catch(_) { process.exit(1) } console.log(handlebars.compile(fs.readFileSync('urls.tpl.js', 'us8'))(urlMap)) }) generator.js urls.js template
‣ RAML ͷఆٛΛίʔυͰར༻͠ɼ༷ŋ࣮ؒΛಉظ RAML ② URL Λ JS ͷมͱͯ͠ఆٛ generator
urls.js frontend backend … RAML Λύʔε const fs = require('fs') const ramlParser = require('raml-parser') const handlebars = require('handlebars') const urlMap = {} const setUrlFromRaml = (data, prefix = '') => { data.resources.forEach(resource => { if (resource.methods) { if (urlMap[resource.methods[0].displayName]) { throw new Error('Duplicated displayName') } urlMap[resource.methods[0].displayName] = { url: `${prefix}${resource.relaUveUri}`, descripUon: resource.methods[0].descripUon } } else { setUrlFromRaml(resource, `${prefix}${resource.relaUveUri}`) } }) } ramlParser.loadFile(‘page.raml').then(data => { try { setUrlFromRaml(data) } catch(_) { process.exit(1) } console.log(handlebars.compile(fs.readFileSync('urls.tpl.js', 'us8'))(urlMap)) }) generator.js urls.js template
‣ RAML ͷఆٛΛίʔυͰར༻͠ɼ༷ŋ࣮ؒΛಉظ RAML ② URL Λ JS ͷมͱͯ͠ఆٛ generator
urls.js frontend backend … displayName Λ key ͱͨ͠ Ϛοϐϯά object Λੜ const fs = require('fs') const ramlParser = require('raml-parser') const handlebars = require('handlebars') const urlMap = {} const setUrlFromRaml = (data, prefix = '') => { data.resources.forEach(resource => { if (resource.methods) { if (urlMap[resource.methods[0].displayName]) { throw new Error('Duplicated displayName') } urlMap[resource.methods[0].displayName] = { url: `${prefix}${resource.relaUveUri}`, descripUon: resource.methods[0].descripUon } } else { setUrlFromRaml(resource, `${prefix}${resource.relaUveUri}`) } }) } ramlParser.loadFile(‘page.raml').then(data => { try { setUrlFromRaml(data) } catch(_) { process.exit(1) } console.log(handlebars.compile(fs.readFileSync('urls.tpl.js', 'us8'))(urlMap)) }) generator.js urls.js template { “page-users-get”: { “url”: “/users”, “descripUon”: “ϢʔβҰཡϖʔδ” }, “page-user-get”: { “url”: “/users/{user_id}”, “descripUon”: “֘͢Δ id ͷϢʔβͷϖʔδ” }, }
‣ RAML ͷఆٛΛίʔυͰར༻͠ɼ༷ŋ࣮ؒΛಉظ RAML ② URL Λ JS ͷมͱͯ͠ఆٛ generator
urls.js frontend backend … generator.js handlebars ͰςϯϓϨʔτʹ Ϛοϐϯά object Λ inject const fs = require('fs') const ramlParser = require('raml-parser') const handlebars = require('handlebars') const urlMap = {} const setUrlFromRaml = (data, prefix = '') => { data.resources.forEach(resource => { if (resource.methods) { if (urlMap[resource.methods[0].displayName]) { throw new Error('Duplicated displayName') } urlMap[resource.methods[0].displayName] = { url: `${prefix}${resource.relaUveUri}`, descripUon: resource.methods[0].descripUon } } else { setUrlFromRaml(resource, `${prefix}${resource.relaUveUri}`) } }) } ramlParser.loadFile(‘page.raml').then(data => { try { setUrlFromRaml(data) } catch(_) { process.exit(1) } console.log(handlebars.compile(fs.readFileSync('urls.tpl.js', 'us8'))(urlMap)) }) urls.js template
‣ RAML ͷఆٛΛίʔυͰར༻͠ɼ༷ŋ࣮ؒΛಉظ RAML ② URL Λ JS ͷมͱͯ͠ఆٛ generator
urls.js frontend backend … / / @flow type URLType = { [string]: { [string]: string } } export const userUrl: URLType = { page: { /** * {{page-users-get.descripUon}} */ users: ‘{{page-users-get.url}}’, /** * {{page-user-user_id-get.descripUon}} */ user: ‘{{page-user-user_id-get.url}}’ } } urls.tpl.js urls.js template
‣ RAML ͷఆٛΛίʔυͰར༻͠ɼ༷ŋ࣮ؒΛಉظ RAML ② URL Λ JS ͷมͱͯ͠ఆٛ generator
urls.js frontend backend … / / @flow type URLType = { [string]: { [string]: string } } export const userUrl: URLType = { page: { /** * ϢʔβҰཡϖʔδ */ users: ‘/users’, /** * ֘͢Δ id ͷϢʔβͷϖʔδ */ user: ‘/user/{user_id}’ } } urls.js urls.js template
‣ RAML ͷఆٛΛίʔυͰར༻͠ɼ༷ŋ࣮ؒΛಉظ RAML ② URL Λ JS ͷมͱͯ͠ఆٛ generator
urls.js frontend backend … import format from 'string-format' import axios from 'axios' import { userUrl } from 'urls' /** * VDOM ͷϨϯμϦϯά */ export const UseLink = ({ user }) => ( <a href={format(userUrl.page.user, { user_id: user.id })}> {user.name} </a> ) /** * API ϦΫΤετ */ export const fetchUserById = user_id => { return dispatch => { axios.get(format(userUrl.api.user, { user_id })) .then(response => /* ... */) .catch(error => /* ... */) } } > const format = require(‘string-format’) undefined > format(‘/user/{user_id}’, { user_id: 1 }) `/user/1` front.js urls.js template
‣ RAML ͷఆٛΛίʔυͰར༻͠ɼ༷ŋ࣮ؒΛಉظ RAML ② URL Λ JS ͷมͱͯ͠ఆٛ generator
urls.js frontend backend … ‘/user/1’ ͷΑ͏ʹల։͞ΕΔ > const format = require(‘string-format’) undefined > format(‘/user/{user_id}’, { user_id: 1 }) `/user/1` import format from 'string-format' import axios from 'axios' import { userUrl } from 'urls' /** * VDOM ͷϨϯμϦϯά */ export const UseLink = ({ user }) => ( <a href={format(userUrl.page.user, { user_id: user.id })}> {user.name} </a> ) /** * API ϦΫΤετ */ export const fetchUserById = user_id => { return dispatch => { axios.get(format(userUrl.api.user, { user_id })) .then(response => /* ... */) .catch(error => /* ... */) } } front.js urls.js template
‣ RAML ͷఆٛΛίʔυͰར༻͠ɼ༷ŋ࣮ؒΛಉظ RAML ② URL Λ JS ͷมͱͯ͠ఆٛ generator
urls.js frontend backend … import format from 'string-format' import Router from 'koa-router' import { userUrl } from 'urls' const router = new Router() router.get( userPageUrl.site.users, (ctx, next) => { /* ... */ } ) router.get( format(userUrl.page.user, { user_id: ':id' }), / / '/user/:id' (ctx, next) => { / / this.params.id Ͱ id Λऔಘ } ) back.js urls.js template
‣ RAML ͷఆٛΛίʔυͰར༻͠ɼ༷ŋ࣮ؒΛಉظ RAML ② URL Λ JS ͷมͱͯ͠ఆٛ generator
urls.js … frontend ‣ RAML ͷ༷Λ1ߦมߋ͢Δ͚ͩͰɼαʔ όŋΫϥΠΞϯτͷίʔυΛҰมߋ͢Δ ͜ͱͳ͠ʹΤϯυϙΠϯτ໊ΛมߋͰ͖Δ #%RAML 0.8 Utle: User version: v1.0 schemas: - User: !include user.json /user: /{user_id}: uriParameters: user_id: type: number get: descripUon: ֘͢Δ id ͷϢʔβͷϖʔδ displayName: page-user-user_id-get … user.raml urls.js template backend
‣ RAML ͷఆٛΛίʔυͰར༻͠ɼ༷ŋ࣮ؒΛಉظ RAML ② URL Λ JS ͷมͱͯ͠ఆٛ generator
urls.js … frontend ‣ RAML ͷ༷Λ1ߦมߋ͢Δ͚ͩͰɼαʔ όŋΫϥΠΞϯτͷίʔυΛҰมߋ͢Δ ͜ͱͳ͠ʹΤϯυϙΠϯτ໊ΛมߋͰ͖Δ #%RAML 0.8 Utle: User version: v1.0 schemas: - User: !include user.json /customer: /{user_id}: uriParameters: user_id: type: number get: descripUon: ֘͢Δ id ͷϢʔβͷϖʔδ displayName: page-user-user_id-get … user.raml urls.js template backend
API Document ③ Valida8on include JSON Schema RAML Valida<on FlowType
Stub Object URL Λ JS ͷ มͱͯ͠ఆٛ
③ Valida8on ‣ is-my-json-valid Ͱ JSON Object ͷϑΥʔϚοτݕূ ‣ ෆਖ਼ϦΫΤετ
API ༷มߋͳͲʹ؆୯ʹؾ͚ͮΔ
③ Valida8on ‣ is-my-json-valid Ͱ JSON Object ͷϑΥʔϚοτݕূ import fs
from 'fs' import path from 'path' import yaml from 'js-yaml' import isMyJsonValid from 'is-my-json-valid' import assert from 'assert' / / Schema ϑΝΠϧͷಡΈࠐΈ const schemaFilePath = path.resolve(__dirname, 'user.yaml') const schema = yaml.safeLoad( fs.readFileSync(schemaFilePath, 'us8'), { schema: yaml.JSON_SCHEMA } ) const userData = { id: 1, name: ‘Yamada Taro’, state: 1 } / / σʔλ͕ఆ͍ͯ͠Δ Format ͔Ͳ͏͔ݕূ const validator = isMyJsonValid(schema) assert(validator(userData), validator.errors) / / ͦͷޙͷॲཧ / / ... --- $schema: hLp:/ /json-schema.org/draA-04/schema# id: user type: object required: - id - name - state properUes: id: descripUon: user id type: number name: descripUon: user's name type: string state: descripUon: user state type: number enum: - 1 # acUve - 2 # inacUve addiUonalProperUes: false validator.js user.yml
③ Valida8on ‣ is-my-json-valid Ͱ JSON Object ͷϑΥʔϚοτݕূ ‣ addi8onalProper8es
‣ ະఆٛͷϓϩύςΟΛड͚͚Δ͔Ͳ͏͔ ‣ maxItems, minItems, uniqueItems ‣ array ͷཁૉͷ࠷େ, ࠷খ, ϢχʔΫੑ ‣ maximum, minimum ‣ number ͷ࠷େ, ࠷খ ‣ maxLength, minLength ‣ string ͷ ࠷େจࣈ, ࠷খจࣈ ‣ paOern ‣ ਖ਼نදݱ ‣ allOf, oneOf, anyOf, not ‣ schema ͷ AND, OR, NOT --- $schema: hLp:/ /json-schema.org/draA-04/schema# id: user type: object required: - id - name - state properUes: id: descripUon: user id type: number name: descripUon: user's name type: string state: descripUon: user state type: number enum: - 1 # acUve - 2 # inacUve addiUonalProperUes: false user.yml
③ Valida8on ‣ is-my-json-valid Ͱ JSON Object ͷϑΥʔϚοτݕূ frontend backend
(node) import import ϑΥʔϜͷ όϦσʔγϣϯ ϦΫΤετϘσΟͷ λϒϧνΣοΫ import fs from 'fs' import path from 'path' import yaml from 'js-yaml' import isMyJsonValid from 'is-my-json-valid' import assert from 'assert' / / Schema ϑΝΠϧͷಡΈࠐΈ const schemaFilePath = path.resolve(__dirname, 'user.yaml') const schema = yaml.safeLoad( fs.readFileSync(schemaFilePath, 'us8'), { schema: yaml.JSON_SCHEMA } ) const userData = { id: 1, name: ‘Yamada Taro’, state: 1 } / / σʔλ͕ఆ͍ͯ͠Δ Format ͔Ͳ͏͔ݕূ const validator = isMyJsonValid(schema) assert(validator(userData), validator.errors) / / ͦͷޙͷॲཧ / / ... validator.js
③ Valida8on ‣ is-my-json-valid Ͱ JSON Object ͷϑΥʔϚοτݕূ frontend import
import ϑΥʔϜͷ όϦσʔγϣϯ ϦΫΤετϘσΟͷ λϒϧνΣοΫ αʔόŋΫϥΠΞϯτؒͰ ဃ͢Δ͜ͱͳ͘ Ξοϓσʔτ͍ͯ͘͜͠ͱ͕Ͱ͖Δ import fs from 'fs' import path from 'path' import yaml from 'js-yaml' import isMyJsonValid from 'is-my-json-valid' import assert from 'assert' / / Schema ϑΝΠϧͷಡΈࠐΈ const schemaFilePath = path.resolve(__dirname, 'user.yaml') const schema = yaml.safeLoad( fs.readFileSync(schemaFilePath, 'us8'), { schema: yaml.JSON_SCHEMA } ) const userData = { id: 1, name: ‘Yamada Taro’, state: 1 } / / σʔλ͕ఆ͍ͯ͠Δ Format ͔Ͳ͏͔ݕূ const validator = isMyJsonValid(schema) assert(validator(userData), validator.errors) / / ͦͷޙͷॲཧ / / ... validator.js backend (node)
API Document include JSON Schema RAML FlowType Stub Object URL
Λ JS ͷ มͱͯ͠ఆٛ Valida<on ④ FlowType
④ FlowType ‣ json-schema-to-flow-type ͰɼJSON Schema ͔Β FlowType Λࣗಈੜ ‣
ಉ͡Α͏ͳΦϒδΣΫτͷೋॏఆ͕ٛͳ͘ͳΔ /* @flow */ export type User = { id: number; name: string; state: 1 | 2; }; import path from 'path' import yaml from 'js-yaml' import { parseSchema } from 'json-schema-to-flow-type' / / Schema ϑΝΠϧͷಡΈࠐΈ const schemaFilePath = path.resolve(__dirname, 'user.yaml') const schema = yaml.safeLoad( fs.readFileSync(schemaFilePath, 'us8'), { schema: yaml.JSON_SCHEMA } ) console.log(`/* @flow */\n\n${parseSchema(schema)}`) json-schema-to-flow.js user-type.js
‣ REST Ҏ֎Ͱ༷ʑͳ IDL ͔Β FlowType Λੜ͢Δڥ ͖͍ͬͯͯΔͷͰɼࣗͰ FlowType ۃྗॻ͔ͳ͍
④ FlowType protobuf thriW protobuf2flowtype thriW2flow user-type.js /* @flow */ export type User = { id: number; name: string; state: 1 | 2; };
API Document include JSON Schema RAML FlowType Stub Object URL
Λ JS ͷ มͱͯ͠ఆٛ Valida<on ⑤ Stub Object
⑤ Stub Object ‣ json-schema-faker Ͱ JSON Schema ͔ΒελϒΦϒδΣΫ τΛࣗಈੜ
‣ ϑϩϯτŋόοΫؒͷεΩʔϚͷΠϯλϑΣʔεͷΈΛ ઌʹఆ͓ٛͯ͘͜͠ͱͰɼαʔό࣮ŋΫϥΠΞϯτ࣮ Λฒߦͯ͠ਐΊΔ͜ͱ͕Ͱ͖Δ ‣ ςετ༰қʹ
⑤ Stub Object ‣ json-schema-faker Ͱ JSON Schema ͔ΒελϒΦϒδΣΫ τΛࣗಈੜ
const fs = require('fs') const path = require('path') const yaml = require('js-yaml') const jsf = require('json-schema-faker') / / Schema ϑΝΠϧͷಡΈࠐΈ const schemaFilePath = path.resolve(__dirname, 'user.yaml') const schema = yaml.safeLoad( fs.readFileSync(schemaFilePath, 'us8'), { schema: yaml.JSON_SCHEMA } ) jsf.resolve(schema).then(result => console.log(JSON.stringify(result, null, 2)) ) { "id": 23, "name": “John Due”, "state": 2 } --- $schema: hLp:/ /json-schema.org/draA-04/schema# id: user type: object required: - id - name - state properUes: id: descripUon: user id type: number faker: random.number name: descripUon: user's name type: string faker: user.findName state: descripUon: user state type: number enum: - 1 # acUve - 2 # inacUve addiUonalProperUes: false user.yml generate-stub.js
⑤ Stub Object ‣ json-schema-faker Ͱ JSON Schema ͔ΒελϒΦϒδΣΫ τΛࣗಈੜ
{ "id": 23, "name": “John Due”, "state": 2 } --- $schema: hLp:/ /json-schema.org/draA-04/schema# id: user type: object required: - id - name - state properUes: id: descripUon: user id type: number faker: random.number name: descripUon: user's name type: string faker: user.findName state: descripUon: user state type: number enum: - 1 # acUve - 2 # inacUve addiUonalProperUes: false const fs = require('fs') const path = require('path') const yaml = require('js-yaml') const jsf = require('json-schema-faker') / / Schema ϑΝΠϧͷಡΈࠐΈ const schemaFilePath = path.resolve(__dirname, 'user.yaml') const schema = yaml.safeLoad( fs.readFileSync(schemaFilePath, 'us8'), { schema: yaml.JSON_SCHEMA } ) jsf.resolve(schema).then(result => console.log(JSON.stringify(result, null, 2)) ) user.yml generate-stub.js
⑤ Stub Object ‣ json-schema-faker Ͱ JSON Schema ͔ΒελϒΦϒδΣΫ τΛࣗಈੜ
‣ ༷ʑͳ face data Λੜ͢Δ͜ͱ͕Ͱ͖Δ ‣ finance ‣ account, amount, bitcoinAddress, … ‣ internet ‣ email, userName, password, … ‣ commerce ‣ productName, price, color, … ‣address ‣ city, zipcode, streetname, … ‣ system ‣ fileName, fileType, filePath, … --- $schema: hLp:/ /json-schema.org/draA-04/schema# id: user type: object required: - id - name - state properUes: id: descripUon: user id type: number faker: random.number name: descripUon: user's name type: string faker: user.findName state: descripUon: user state type: number enum: - 1 # acUve - 2 # inacUve addiUonalProperUes: false user.yml
⑤ Stub Object ‣ raml-server Ͱ mock API Λੜ͢Δ͜ͱͰ͖Δ ‣
JSON Schema ͷ include Մೳ $ raml-server user.raml Running RAML server on localhost:3000... $ curl hLp:/ /localhost:3000/user/1 { "id": 23, "name": “quam esse consectetur”, "state": 2 }
Conclusion
‣ JSON Schema ͱ RAML Λϝϯςφϯε͍ͯ͘͜͠ͱͰɼ API ༷Λ҆શ & ༰қʹΞοϓσʔτ͍ͯ͘͜͠ͱ͕Ͱ͖Δ
Conclusion JSON Schema RAML include API Document URL Λ JS ͷ มͱͯ͠ఆٛ Valida<on FlowType Stub Object
Conclusion Service Service Service Service API Spec v1.0 v1.0 v1.0
v1.0 v1.0 ‣ JSON Schema ͱ RAML Λϝϯςφϯε͍ͯ͘͜͠ͱͰɼ API ༷Λ҆શ & ༰қʹΞοϓσʔτ͍ͯ͘͜͠ͱ͕Ͱ͖Δ ‣ ٯʹݴ͏ͱɼAPI ༷ΛΞοϓσʔτ͠ͳ͍ͱɼ࣮ʹมߋ ΛՃ͑Δ͜ͱ͕Ͱ͖ͳ͍ (SpecificaUon Driven Development) ‣ ݫີ͕͞ॊೈ͞ΛੜΈग़͢