Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
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
"計画的無茶振り"を通してAIネイティブ世代の新人を育てる
subroh0508
1
120
[re:Invent2026事前勉強会(有志開催)]続きは、ラスベガスの廊下で / continue-in-the-vegas-hallway
sh_fk2
0
350
スクラム祭り2026福井トラック_閉じたループから価値のスパイラルへ
kc_nakanishi
0
190
Maestro MCPとClaude Code Skillsによる自律的なE2Eテスト支援
asmz
0
300
Claude Codeから見る、エンジニアの世界の変容とその先
oikon48
7
4.6k
契約で守るコンパウンドプロダクトのデザインシステム
yug1224
1
1.6k
200人の配信コメント、 全部Jevが仕分けます 〜 マサカリガードを作ってみた 〜
katayamatg
1
360
OneLakeをデータソースにした AI Search(Foundry IQ)の ライフサイクル管理
ryuseiiida
0
130
推論基盤と Scale-Up Interconnect の現状と制約から俯瞰する技術動向
markunet
2
560
日本AIベンチャーレポート2026
kentarofujii
0
250
Flutter Widget Previewerで始めるUIプレビューとVRTの自動化
sakusaku3939
0
150
AI時代の開発はウォーターフォールに回帰する? / development-with-ai-back-to-wf
okamototakuyasr2
0
140
Featured
See All Featured
A better future with KSS
kneath
240
18k
Mobile First: as difficult as doing things right
swwweet
225
10k
Reality Check: Gamification 10 Years Later
codingconduct
0
2.4k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
23k
<Decoding/> the Language of Devs - We Love SEO 2024
nikkihalliwell
1
370
Lightning talk: Run Django tests with GitHub Actions
sabderemane
0
300
Ten Tips & Tricks for a 🌱 transition
stuffmc
1
270
Site-Speed That Sticks
csswizardry
13
1.6k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
287
14k
Measuring Dark Social's Impact On Conversion and Attribution
stephenakadiri
2
310
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
254
22k
Introduction to Domain-Driven Design and Collaborative software design
baasie
1
1.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) ‣ ݫີ͕͞ॊೈ͞ΛੜΈग़͢