JSON Schema Centralized Design

Eeff1c9b0fb81b813e97dc8436c1b0f8?s=47 pika_shi
November 26, 2017

JSON Schema Centralized Design

Node Fest 2017

Eeff1c9b0fb81b813e97dc8436c1b0f8?s=128

pika_shi

November 26, 2017
Tweet

Transcript

  1. JSON Schema Centralized Design Node Fest Tokyo 2017 (2017/11/26) @pika_shi

    - Hikaru Takemura (JSON Schema த৺ઃܭ)
  2. ‣ Hikaru Takemura ‣ @pika_shi ‣ FOLIO ‣ Frontend Engineer

    (React, Node) ‣ AdriaBlue ‣ Mobile App Developer (SwiA)
  3. API Specifica<on

  4. API Specifica8on ‣ PROS ‣ API ఆٛΛ໌จԽ͓ͯ͘͜͠ͱͰɼϑϩϯτŋόοΫؒͰ ࣮૷ΛεϜʔζʹਐΊΒΕΔ ‣ body

    ͷܕఆٛΛݫີʹ͓͜ͳ͏͜ͱ͕Ͱ͖Δ ‣ ੬ऑੑ਍அ౳Ͱͷ URL εΩϟϯͷࡍʹར༻Ͱ͖Δ
  5. API Specifica8on ‣ CONS ‣ ࢓༷ͱ࣮૷͕ঃʑʹဃ཭͍ͯ͘͠ ‣ ͦͷ݁Ռɼࢀর͢΂͖৘ใ͕෼͔ΒͣɼMicroservices ؒŋϓϩδΣΫτ಺Ͱίϛϡχέʔγϣϯʹᴥᴪ͕ੜ͡Δ Service

    Service Service Service API Spec
  6. Mo8va8on ‣ ࢓༷ͱ࣮૷͕ဃ཭͢Δͷ͸ɼͦΕΒ͕ಠཱʹϝϯςφϯε ͞ΕΔ͔Β ‣ ϝϯςφϯε͢Δ΋ͷΛ 1 ͭʹ͠ɼ࢓༷ŋ࣮૷ؚΊ͢΂ͯͦ ͷ৘ใΛࢀর͢ΔΑ͏ʹ͍ͨ͠ Service

    Service Service Service API Spec
  7. JSON Schema & RAML

  8. JSON Schema ‣ JSON Object ͷܕఆٛϑΥʔϚοτ ‣ JSON Ͱهड़ (YAML

    Ͱهड़͢Δ৔߹͕ଟ͍) ‣ minimum, maximum ͔Βɼਖ਼نදݱΛѻ͑Δ paLern ·Ͱɼ༷ʑͳϓϩύςΟ͕ఆٛ͞Ε͍ͯΔ
  9. 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
  10. RAML ‣ REST API ఆٛϑΥʔϚοτ ‣ YAML Ͱهड़ ‣ ࢓༷Λόʔδϣϯ؅ཧͰ͖ɼมߋ΋

    diff Ͱ؅ཧͰ͖Δ ‣ JSON Schema Λ include Ͱ͖Δ ‣ ڞ௨෦෼ΛఆٛͰ͖ɼ࠶ར༻͠΍͍͢ ‣ ଞʹ΋ Swagger, Open API, API Blueprint ౳͕͋Δ
  11. 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
  12. 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: Ϣʔβ৘ใ͕ଘࡏ͠ͳ͍৔߹
  13. JSON Schema Centralized Design (JSON Schema த৺ઃܭ)

  14. ‣ JSON Schema ͱ RAML Λத৺ʹਾ͑ͨΤίγεςϜΛߏங JSON Schema Centralized Design

    JSON Schema RAML include API Document URL Λ JS ͷ ม਺ͱͯ͠ఆٛ Valida<on FlowType Stub Object
  15. ‣ JSON Schema ͱ RAML Λத৺ʹਾ͑ͨΤίγεςϜΛߏங JSON Schema Centralized Design

    JSON Schema RAML include API Document URL Λ JS ͷ ม਺ͱͯ͠ఆٛ Valida<on FlowType Stub Object ϝϯςφϯε͢Δͷ͸͜͜ͷΈ
  16. API Document URL Λ JS ͷ ม਺ͱͯ͠ఆٛ ① API Document

    include JSON Schema RAML Valida<on FlowType Stub Object
  17. ① API Document ‣ raml2html Ͱ RAML ͔Β API υΩϡϝϯτΛੜ੒

    ‣ γϯϓϧ͔ͭΠϯλϥΫςΟϒͳ HTML υΩϡϝϯτ
  18. ‣ raml2html Ͱ RAML ͔Β API υΩϡϝϯτΛੜ੒ ‣ γϯϓϧ͔ͭΠϯλϥΫςΟϒͳ HTML

    υΩϡϝϯτ ① API Document JSON Schema
  19. URL Λ JS ͷ ม਺ͱͯ͠ఆٛ API Document ② URL Λ

    JS ͷม਺ͱͯ͠ఆٛ include JSON Schema RAML Valida<on FlowType Stub Object
  20. ② 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
  21. ‣ RAML ͷఆٛΛίʔυͰ΋ར༻͠ɼ࢓༷ŋ࣮૷ؒΛಉظ urls.js template RAML ② URL Λ JS

    ͷม਺ͱͯ͠ఆٛ generator urls.js frontend backend …
  22. ‣ 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
  23. ‣ 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
  24. ‣ 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
  25. ‣ 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
  26. ‣ 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
  27. ‣ 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
  28. ‣ 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 ͷϢʔβͷϖʔδ” }, }
  29. ‣ 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
  30. ‣ 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
  31. ‣ 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
  32. ‣ 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
  33. ‣ 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
  34. ‣ 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
  35. ‣ 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
  36. ‣ 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
  37. API Document ③ Valida8on include JSON Schema RAML Valida<on FlowType

    Stub Object URL Λ JS ͷ ม਺ͱͯ͠ఆٛ
  38. ③ Valida8on ‣ is-my-json-valid Ͱ JSON Object ͷϑΥʔϚοτݕূ ‣ ෆਖ਼ϦΫΤετ΍

    API ࢓༷มߋͳͲʹ؆୯ʹؾ͚ͮΔ
  39. ③ 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
  40. ③ 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
  41. ③ 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
  42. ③ 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)
  43. API Document include JSON Schema RAML FlowType Stub Object URL

    Λ JS ͷ ม਺ͱͯ͠ఆٛ Valida<on ④ FlowType
  44. ④ 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
  45. ‣ REST Ҏ֎Ͱ΋༷ʑͳ IDL ͔Β FlowType Λੜ੒͢Δ؀ڥ͸ ੔͖͍ͬͯͯΔͷͰɼࣗ෼Ͱ FlowType ͸ۃྗॻ͔ͳ͍

    ④ FlowType protobuf thriW protobuf2flowtype thriW2flow user-type.js /* @flow */ export type User = { id: number; name: string; state: 1 | 2; };
  46. API Document include JSON Schema RAML FlowType Stub Object URL

    Λ JS ͷ ม਺ͱͯ͠ఆٛ Valida<on ⑤ Stub Object
  47. ⑤ Stub Object ‣ json-schema-faker Ͱ JSON Schema ͔ΒελϒΦϒδΣΫ τΛࣗಈੜ੒

    ‣ ϑϩϯτŋόοΫؒͷεΩʔϚͷΠϯλϑΣʔεͷΈΛ ઌʹఆ͓ٛͯ͘͜͠ͱͰɼαʔό࣮૷ŋΫϥΠΞϯτ࣮૷ Λฒߦͯ͠ਐΊΔ͜ͱ͕Ͱ͖Δ ‣ ςετ΋༰қʹ
  48. ⑤ 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
  49. ⑤ 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
  50. ⑤ 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
  51. ⑤ 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 }
  52. Conclusion

  53. ‣ JSON Schema ͱ RAML Λϝϯςφϯε͍ͯ͘͜͠ͱͰɼ API ࢓༷Λ҆શ & ༰қʹΞοϓσʔτ͍ͯ͘͜͠ͱ͕Ͱ͖Δ

    Conclusion JSON Schema RAML include API Document URL Λ JS ͷ ม਺ͱͯ͠ఆٛ Valida<on FlowType Stub Object
  54. 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) ‣ ݫີ͕͞ॊೈ͞ΛੜΈग़͢