Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Frontend Re-architecture of a decade-old Rails App

Ken Wagatsuma
November 13, 2018

Frontend Re-architecture of a decade-old Rails App

Keywords

- Refactoring
- BFF (Backend-For-Frontend)
- GraphQL
- React.js
- Apollo Client
- Snapshot Testing with Jest
- React Storybook
- ESLint
- Atomic Designs

Ken Wagatsuma

November 13, 2018
Tweet

More Decks by Ken Wagatsuma

Other Decks in Programming

Transcript

  1. Frontend Re-architecture of a decade-old Rails App w/ BFF/GraphQL/React Cookpad

    Inc. Kenju Wagatsuma 2018/11/13 @ Rails Tokyo Meetup
  2. Who? Kenju Wagatsuma • Backend Engineer (Ruby/Go/AWS) • Streaming Pipeline

    ❤ • Recruit HD ~ Bit Journey ~ Cookpad https://github.com/kenju https://twitter.com/itiskj
  3. Targeting • Extreme Audience Targeting (EAT) • default user segments

    • “Professional-oriented”, “healthy”, “kids”, … etc. • Customized EAT • optimized user segments • Shopping Time Hijack • 17:00 - 19:00 • SOV (= Share of Voice) 100%
  4. database.yml EBUBCBTFZNM BENJO  QSPEVDUJPO@BE  QSPEVDUJPO@CBTF "E%FMJWFSZ%#  QSPEVDUJPO@BE

     QSPEVDUJPO@CBTF .BJO DPPLQBE   QSPEVDUJPO  QSPEVDUJPO@CBTF %8) 3FE4IJGU   QSPEVDUJPO@EXI  EXI@CBTF 3 + 1 database config One for the admin app One for the Cookpad app One for the ad delivery app The last one for Redshift
  5. How old are you, admin app? > git show $(git

    rev-list --max-parents=0 HEAD)
  6. This is “Legacy” • First git commit is in 2008

    • Don’t make fun of me. The first commit was “imported from SVN to Git” • The fortunate is… • The former team members did a lot of great refactoring • The codebase is not as large as the Cookpad application
  7. batch MySQL Operation Team batch MySQL write write admin Campaign

    Product Creative Week Frame DeliveryPlan DeliveryPayload TargetingRule Site Goal AdFormat CampaignPeriod CreativeTarget Delivery Booking Old New
  8. It’s SO HARD to maintain • Which model is which,

    old or for new one? • Incoherent Business Logic Implementation for Frontend • Controller, Services, ViewHelper, ViewModel… • Old libraries • jQuery 1.x (latest = 3.x) • Bootstrap 3.x (latest = 4.x) • Not used package.json dependencies (…!!??) • Migration on the way (e.g. CoffeeScript w/ ES6, many TODOs) • No JavaScript Unit Testing
  9. BFF = Backend For Frontend • “Pattern: Backends For Frontends”

    by Sam Newman, the author of “Building Microservices” • Definition: “Single-purpose Edge Services for UIs and external parties”
  10. Why BFF: 3 Use Cases by @yosuke_furukawa, Node.js collaborator 1.

    Accelerate Development for Frontend/Backend Developers 2. Special Use-cases (e.g. Server-Side Rendering for SEO, WebSocket for Real-time app) 3. Rearchitect Legacy System
  11. What is GraphQL? GraphQL … - is an open source

    query language created by Facebook - as an alternative to the common REST architecture - gives clients more control over what information is sent - is NOT opinionated about the network layer & payload format - which is often HTTP & JSON - is NOT opinionated about the application architecture - “GraphQL is only a query language”
  12. batch MySQL Operation Team batch MySQL write write admin Campaign

    Product Creative Week Frame DeliveryPlan DeliveryPayload TargetingRule Site Goal AdFormat CampaignPeriod CreativeTarget Delivery Booking Old New
  13. Operation Team admin Campaign Product Creative DeliveryPlan DeliveryPayload Site Goal

    batch write Week Frame TargetingRule Delivery batch write AdFormat CampaignPeriod CreativeTarget Booking Old New
  14. Operation Team admin Campaign Product Creative DeliveryPlan DeliveryPayload Site Goal

    batch write AdFormat CampaignPeriod CreativeTarget Booking
  15. Query query { campaign(id: 19000) { id status creatives {

    id campaignId } } } { "data": { "campaign": { "id": "19000", "status": 0, "creatives": [ { "id": "61031", "campaignId": 19000 } ] } } } Query Example Response in JSON
  16. Schema type Campaign { creatives: [Creative!] id: ID! status: Int!

    } type Creative { campaignId: Int id: ID! text: String } module Types class BaseObject < GraphQL::Schema::Object end end class Types::CampaignType < Types::BaseObject field :id, ID, null: false field :status, Integer, null: false field :creatives, [Types::CreativeType], null: true end class Types::CreativeType < Types::BaseObject field :id, ID, null: false field :campaign_id, Integer, null: true field :text, String, null: true end schema.graphql schema in Ruby (graphql gem)
  17. Query Implementation with “graphql” gem class Types::QueryType < Types::BaseObject description

    "The query root of this schema” field :campaign, Types::CampaignType, null: true do description "Campaign" argument :id, ID, required: true end def campaign(id:) Campaign.find(id) end end query_type.rb
  18. Setup class MySchem < GraphQL::Schema query(Types::QueryType) # NOTE: # add

    mutation, middle-wares, # complexity and other config here end class GraphqlController < ApplicationController def execute result = MySchema.execute( params[:query], variables: params[:variables], context: { login_user: current_user, }, operation_name: params[:operationName]) render json: result end end my_schema.rb graphql_controller.rb
  19. Our GraphQL Design Principles • ✅ GraphQL server should be

    as thin as possible • write minimum tests for GraphQL • “Graph is a thin layer” by Lee Byron, the co- creator of GraphQL & ex-Facebook engineer • ✅ Implement gradually • do NOT write all schemas at once • write schemas ONLY needed for client IUUQTXXXHSBQIRMDPNBSUJDMFTZFBSTPGHSBQIRMMFFCZSPO
  20. JavaScript on Our Admin Rails App Before After No JavaScript

    Unit Test Snapshot Testing w/ Jest CoffeeScript & some ES6 Pure ES6 (ES6-Oriented ESLint Rules) jQuery React.js & Apollo Client (still some jQuery) - React Storybook
  21. Snapshot Testing w/ Jest import React from 'react'; import Loading

    from '../../../app/javascript/ components/atoms/Loading'; import renderer from 'react-test-renderer'; test('Loading', () => { const component = renderer.create( <Loading></Loading> ); const tree = component.toJSON(); expect(tree).toMatchSnapshot(); }); // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Loading 1`] = ` <div> ... Loading <img alt="indicator" src="/images/lib/indicator.gif" /> </div> `; __tests__/components/Loading.test.js __snapshots__/Loading.test.js.snap
  22. Our Design Guidelines (Beta) • Improve DX (Developer Experience) •

    “Developing the internal admin app” should be a valuable experience for their career • Avoid the Bleeding Edge • no dedicated frontend engineers -> maintenance costs • Lay the rails for junior & future developers • “Once you see the code, you will know how you write” • follow well-known best practices
  23. Design Guidelines in Practice • Bootstrap • well-known technology, easy

    to develop • Systematic Styling Architecture • styled-components? Still under consideration • “Atomic Design” • to achieve reusability
  24. Our Atomic Design Guideline (Beta) Component Definition Example Atoms minimum

    components button/input/toast Molecules handle single event composed of atoms toast/pager Organisms handle multiple events use GraphQL provider charts/form Templates wireframe level - Pages per #index or #show actions -
  25. How much “Legacy”? • Original members already left the team

    • become friends with git blame / git log • broken URL to deprecated SaaS (e.g. Firebase) • Multiple Versioning • versioning for delivery, versioning for targeting, versioning for Web/iOS/Android SDKs • & they are independent from each other • No refactoring expert • 20 % Rule for almost 1 year
  26. Is Legacy System “evil”? • Harder to maintain • “What

    is this module in lib/ ???” • Harder to add new features • “OMG, somehow feature spec failed” • Harder to explain architecture to newcomers • “What is the difference between ‘CampaignController’ & ‘CampaignsController’?”
  27. Mindset of Refactoring • Being Respectful to the current codebase

    • this is why you are here and receive salary • Re-architect from “the ideal”, not “the codebase” • making decision from … current business requirements, member’s skill level, impact range & risk of changes • Don’t confuse means and ends • Why refactoring? Because to drive business growth • Type System/Static Analysis/Microservices/CI … they all means, not ends
  28. How to “Fight” with Legacy First of all, “Understanding” the

    codebase by “Exploratory Refactoring” “Re-Engineering Legacy Software” by Chris Birchall 6OEFSTUBOEJOH 7JTVBMJ[F 2VBMJUZX5FTU 3FGBDUPS
  29. How to “Fight” with Legacy Secondly, visualize quality with tests

    • Write regression tests to protect you weekend calls • Before refactoring drastically, write test first to “understand” the codebase more 6OEFSTUBOEJOH 7JTVBMJ[F 2VBMJUZX5FTUT 3FGBDUPS
  30. How to “Fight” with Legacy 6OEFSTUBOEJOH 7JTVBMJ[F 2VBMJUZX5FTU 3FGBDUPS Finally,

    do refactor. No silver bullet. Rome was not built in a day. • Readability • Design Patterns • OOP • Encapsulation
  31. Make “Right” Decision “When the future cost of doing nothing

    is the same as the current cost, postpone the decision.”
  32. Mobile client Client delivery admin batch admin MySQL MySQL Operation

    Team batch cookpad MySQL delivery cookpad mobile write write Old New
  33. Delivery - DeliveryPlan { "version": 1, "site_id": 3, "product_id": 537,

    "product_capacity": 1, "campaigns": [ { "id": 18585, "goal_type": "percentage", "creatives": [ { "id": 60057, "formats": [ "category_hijack_spw_with_image" ], "targets": [ { "keywords": [ "͘Ζ·Ί", "ΫϩϚϝ" ] } ] } ], "rate_numerator": 10000, "rate_denominator": 10000 },
  34. Delivery - DeliveryPayload [ { "key": "cookpad-android-search-master", "creatives": [ [

    { "id": 111, "template": "image", "click_url": "https://...", "media": { "mime_type": "image/jpeg", "original": "https://example.com/d6663ab44a0c48a1996debc690893493.jpg?1510564210", "width": 300, "height": 250 } } ] ] }