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
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
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
AWS Security Hub CSPMの成功・失敗体験
cmusudakeisuke
0
540
LayerX コーポレートエンジニアリング室におけるサプライチェーンセキュリティへの取り組み / Supply Chain Security at LayerX Corporate Engineering
yuyatakeyama
3
840
2026 AI Memory Architecture
nagatsu
0
190
元・セキュリティ学習経験0大学生による業務紹介 / An Introduction to the Job by a Former College Student with Zero Security Training Experience
nttcom
0
100
SteampipeとExcel Power QueryでAWS構成定義書の作成を自動化する
jhashimoto
0
180
事業会社における 機械学習・推薦システム技術の活用事例と必要な能力 / ml-recsys-in-layerx-wantedly-2026
yuya4
0
160
從開發到部署全都交給 AI:實作 AI 驅動的自動化流程
appleboy
0
160
IaC コードを資産へ:AWS CDK 社内ライブラリと横断展開 / aws-summit-japan-2026
gotok365
10
1.6k
Claude Codeをどのように キャッチアップしているか
oikon48
13
8.8k
飲食店もAIで。レジ締めやハンディシステムをつくってる話 / Using AI for restaurant management
vtryo
0
170
クレデンシャル流出 ― 攻撃 3 時間 vs 復旧 10 時間。この非対称性にどう備えるか
kazzpapa3
3
560
AWS Security Agent といっしょに脅威モデリングをやってみよう
amarelo_n24
1
210
Featured
See All Featured
How to make the Groovebox
asonas
2
2.2k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
231
55k
Exploring the relationship between traditional SERPs and Gen AI search
raygrieselhuber
PRO
2
4k
A Tale of Four Properties
chriscoyier
163
24k
Context Engineering - Making Every Token Count
addyosmani
9
980
Design of three-dimensional binary manipulators for pick-and-place task avoiding obstacles (IECON2024)
konakalab
0
470
The Hidden Cost of Media on the Web [PixelPalooza 2025]
tammyeverts
2
330
YesSQL, Process and Tooling at Scale
rocio
174
15k
BBQ
matthewcrist
89
10k
Large-scale JavaScript Application Architecture
addyosmani
515
110k
Reality Check: Gamification 10 Years Later
codingconduct
0
2.2k
The Spectacular Lies of Maps
axbom
PRO
1
820
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) ‣ ݫີ͕͞ॊೈ͞ΛੜΈग़͢