Automatic Code Generation for SPA

Automatic Code Generation for SPA

851704b2aa97d2117dc89bf60a2cd272?s=128

tanakaworld

July 03, 2019
Tweet

Transcript

  1. 2.

    About Me • Twitter: @_tanakaworld • GitHub: tanakaworld • Merpay

    ◦ Software Engineer (Frontend) ◦ CSTool / MSTool
  2. 3.

    1. What is Code Generation? 2. Tell about the Real

    World Rearchtecture project 3. Tips of Generator Agenda
  3. 6.

    Schema Driven Development • Make Development flow more efficiently •

    Automatic ◦ Source Code Generation ◦ Document Generation ◦ Testing
  4. 7.

    Microservices in Merpay Protocol Buffers • gRPC • Write Schema

    by Protocol Buffers • Generate Client and Server
  5. 8.

    Protocol Buffers service SampleAPI { rpc GetUser(GetUsersRequest) returns (GetUsersResponse) {

    option (google.api.http) = { get : "/users" body: "*" }; } } message GetUsersRequest {} message GetUsersResponse { repeated User user = 1; } .proto
  6. 10.

    .proto to TypeScript export namespace SampleAPI { export class GetUser

    implements APIRequest<GetUsersRequest, GetUsersResponse> { _response?: GetUsersResponse; path = "/users"; method: HTTPMethod = "get"; constructor(public parameter: GetUsersRequest) {} } } export interface GetUsersRequest {} export interface GetUsersResponse { user?: User[]; }
  7. 12.

    REAL WORLD Rearchitecture Project 1. Overview 2. Backend Implementation 3.

    API schema 4. Generator 5. Frontend Implementation
  8. 21.

    Why fast_jsonapi was developed? • Netflix ◦ always use JSON:API

    • AMS is too flexible ◦ AMS is designed to serialize JSON in several different formats, not just JSON:API
  9. 25.
  10. 27.

    TL;DR; API • Resource • Request • Response • Method

    • URL • Params RSpec Schema /app/books
  11. 28.

    • https://github.com/fotinakis/swagger-blocks • Works with all Ruby web frameworks •

    100% support for all features of the Swagger 2.0 spec • Can use Ruby DSL to write OpenAPI schema swagger-blocks
  12. 29.

    # app/models/concerns/swagger/book_schema.rb module Swagger::BookSchema extend ActiveSupport::Concern include Swagger::Blocks included do

    swagger_schema :Book, required: [:title, :description, :image_url] do property :id, type: :integer property :title, type: :string property :description, type: :string property :created_at, type: :string property :updated_at, type: :string end end end
  13. 33.

    openapi-generator • https://github.com/OpenAPITools/openapi-generator • It allows generation from OpenAPI Spec

    ◦ API client libraries ◦ Server stubs ◦ Documentation ◦ Configuration
  14. 34.
  15. 36.

    Generate openapi-generator generate \ -i ./tmp/swagger.json \ -g typescript-axios \

    -o ./app/javascript/src/gen/ --additional-properties="modelPropertyNaming=snake_ case" https://github.com/OpenAPITools/openapi-generator/blob/master/docs/generators/typescript-axios.md
  16. 37.

    export interface Book { id?: number; title: string; description: string;

    image_url: string; created_at?: string; updated_at?: string; } export interface BookResponse { id: string; type: string; attributes: Book; } export interface CreateBookRequest { title?: string; description?: string; image?: object; }
  17. 38.

    export class SampleAppApi extends BaseAPI { public createBook(title?: string, description?:

    string, image?: object, options?: any) { ••• } public deleteBook(id: number, options?: any) { ••• } public getBook(id: number, options?: any) { ••• } public getBooks(options?: any) { ••• } public updateBook(id: number, title?: string, description?: string, image?: object, options?: any) { ••• } }
  18. 41.

    Overview • Render SPA bundle via Rails View • with

    Turbolinks • SPA ◦ CSR ◦ Vue ◦ Vue Router ◦ Vuex API Client /app/books SPA
  19. 42.

    Rails Routing Rails.application.routes.draw do // path for SPA // app/books

    // app/books/new // app/books/1 // app/books/1/edit // ••• match 'app/(*spa_path)' => 'spa_app#index', via: :get end
  20. 44.

    How it works with Turbolinks • Make Vue instance on

    'turbolinks:load' • Destroy Vue instance on 'turbolinks:visit' • ref: https://qiita.com/midnightSuyama/items/efc5441a577f3d3abe74
  21. 45.

    Make/Destroy Vue instances document.addEventListener('turbolinks:load', () => { const templates =

    document.querySelectorAll('[data-vue]'); templates.forEach((el: HTMLElement) => { const option = options[el.dataset.vue] const vm = new Vue(Object.assign(option, { el })); vms.push(vm); }); }); document.addEventListener('turbolinks:visit', () => { for (let vm of vms) { vm.$destroy(); } vms = []; });
  22. 46.

    Use require.context of webpack // app/javascript/packs/vue-turbolinks.ts const options = {};

    const requireContext = require.context('../options', false, /\.ts$/); requireContext.keys().forEach(key => { const name = key .split('/') .pop() .split('.') .shift(); options[name] = requireContext(key).default; });
  23. 48.

    Vuex Store Implementation • Use generated API Client in Store

    • FSA = Flux Standard Action ◦ https://github.com/redux-utilities/flux-standard-action ◦ https://github.com/sue71/vuex-typescript-fsa
  24. 49.

    Vuex + FSA store.dispatch('increment') // or store.dispatch({ type: 'increment', meta:

    null, error: null, payload: { ••• } }) https://vuex.vuejs.org/guide/actions.html#dispatching-actions
  25. 50.

    import { actionCreatorFactory } from 'vuex-typescript-fsa/lib'; import { UpdateBookRequest }

    from '@gen'; export const namespace = 'books'; const actionCreator = actionCreatorFactory(namespace); export const GetBook = actionCreator<{ id: number; }>( 'GET_BOOK' ); Define FSAs
  26. 52.

    Actions / Mutations / Getters export const module: Module<State, RootState>

    = { namespaced: true, actions: combineAction( action(GetBook, async function(context, action) { const { data } = await this.sampleAppAPI .getBook(action.payload.id); context.commit(BookReceived(data.data)); }) ) };
  27. 55.

    Error: URLSearchParams is not defined import * as url from

    'url'; // Error const params = url.URLSearchParams(•••);
  28. 57.

    Override ‘url’ package (Monkey patch) // webpacker config resolve: {

    alias: { '@': path.resolve(__dirname, '../../app/javascript/src'), urlOriginal: path.resolve(__dirname, '../../node_modules/url'), url: path.resolve(__dirname, './extensions/url') } } // config/webpack/extensions/url.js import { parse, resolve, resolveObject, format, Url } from 'urlOriginal'; import URLSearchParams from '@ungap/url-search-params'; export { parse, resolve, resolveObject, format, Url, URLSearchParams };
  29. 60.

    Heavy Base64 Image • Easy to upload image as Base64

    String in Rails ◦ https://github.com/y9v/carrierwave-base64 ◦ Able to send image via ‘application/json’ ◦ size tooooooo big • Use multipart/form-data instead
  30. 62.

    e.g. ‘url’ // ‍♂ This overlaps with import * url

    from 'url' if (url !== undefined) { localVarFormParams.append('url', url as any); } // ‍♂ rename to '_url' if (_url !== undefined) { localVarFormParams.append('_url', _url as any); } // convert in Rails params.tap {|p| p[:url] = p[:_url]}.permit( :title, :description, :url, :image )
  31. 63.

    fixed now • [TS][Axios] To fix conflict params name 'url'

    ◦ https://github.com/OpenAPITools/openapi-generator/pull/2921
  32. 65.

    Put .jar into node_modules/ // @openapitools/openapi-generator-cli/bin/openapi-generator const binPath = resolve(__dirname,

    'openapi-generator.jar'); const JAVA_OPTS = process.env['JAVA_OPTS'] || ''; let command = `java ${JAVA_OPTS} -jar "${binPath}"`;
  33. 66.

    Generator is changed every day. • You can download and

    use snapshot • https://oss.sonatype.org/content/repositories/ snapshots/org/openapitools
  34. 67.
  35. 69.

    Pro/Con • Pro ◦ Able to detect unexpected changes ◦

    Reduce change by hand ◦ Able to reuse API Client for another platform ◦ Automatically detect diff • Con ◦ Took time to setup ◦ Too many things to do ◦ Too much for single person project
  36. 71.
  37. 72.
  38. 73.

    OpenAPI + JSON:API is Bad approach? • https://apisyouwonthate.com/blog/json-api-openapi-and-json-schema-working-i n-harmony •

    “At WeWork, we use JSON Schema to describe the data models, OpenAPI to describe everything else, then the message being described is usually JSON API.”
  39. 74.

    ※ Fast JSON API is not a replacement for AMS

    • AMS does many things and is very flexible ◦ This is why fast_jsonapi is more faster • Netflix still use AMS for non JSON:API serialization and deserialization • ref: Performance methodology