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
Introduction to Sansan, inc / Sansan Global Development Center, Inc.
sansan33
PRO
0
3.2k
OAuth SPIFFE Client Authentication(OAuth/OIDC Numa (Immersion) Workshop 2026)
oidfj
PRO
0
350
1.5時間を無駄にして学んだwsl2におけるaptとsnapの選択と仕組み
yosaka0123
0
460
AI駆動開発はどこまで来たのか? ファインディの最新実態調査で読み解く現在地 Devin Con Tokyo
akiratom
4
1.9k
現場の暗黙知を継承するAIエージェント — 対話から生まれる長期記憶と Skills
atsukish
0
130
Oracle AI Databaseデータベース・サービス: BaseDB/ExaDB-Dの可用性
oracle4engineer
PRO
1
960
hookで自作する Claude Code の「実況ボード」
shinyasaita
2
230
Microsoft MVP プログラムを紹介するから目指す人増えてくれ
tsubakimoto_s
0
140
dbt in Microsoft Fabric
ryomaru0825
0
300
EMの役割で 変わったこと・変わらなかったこと
sansantech
PRO
1
190
暗号化?某ファイルストレージはどうなるの!? 3rd Partyとうまく付き合う秘密度ラベル設計
kasada
0
210
OpenID for Verifiable Credentials 実装から見えた相互運用性確保までの道のり(OAuth/OIDC Numa (Immersion) Workshop 2026)
oidfj
PRO
0
360
Featured
See All Featured
Discover your Explorer Soul
emna__ayadi
2
1.3k
How to Think Like a Performance Engineer
csswizardry
28
2.7k
The untapped power of vector embeddings
frankvandijk
2
1.8k
GraphQLとの向き合い方2022年版
quramy
50
15k
The Spectacular Lies of Maps
axbom
PRO
1
950
Paper Plane (Part 1)
katiecoart
PRO
1
10k
New Earth Scene 8
popppiees
3
2.5k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
232
55k
First, design no harm
axbom
PRO
2
1.3k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
27k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
128
56k
Facilitating Awesome Meetings
lara
57
7.1k
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) ‣ ݫີ͕͞ॊೈ͞ΛੜΈग़͢