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

Salariesを支えるチーム開発の現場

techtekt
November 11, 2021

 Salariesを支えるチーム開発の現場

techtekt

November 11, 2021
Tweet

More Decks by techtekt

Other Decks in Programming

Transcript

  1. *OEFY ❏ ಋೖ ❏ ॴଐ෦ॺ঺հ ❏ 4BMBSJFT঺հ ❏ νʔϜ։ൃ ❏

    4BMBSJFTνʔϜͷ։ൃମ੍ͳͲͷ঺հ ❏ νʔϜ։ൃΛࢧ͑ΔϑϩϯτΤϯυ ❏ νʔϜ։ൃΛࢧ͑ΔόοΫΤϯυ
  2. ࣗݾ঺հ ❏ ࠤ઒ ༤ඈ ❏ ೥݄ ύʔιϧΩϟϦΞೖࣾ ❏ ৬ྺ ❏

    &$γεςϜͷύοέʔδ։ൃ ❏ ҿ৯ళͰͷ଺ࡏ࣌ؒ؅ཧγεςϜ։ൃ ❏ ͓ห౰୐഑αʔϏε։ൃ ❏ ٕज़ ❏ /FYUKT /VYUKT 8FC$PNQPOFOUT
  3. αʔϏε։ൃ෦ ֓ཁ ॴଐ෦ॺ ςΫϊϩδʔຊ෦ ΤϯδχΞϦϯά౷ׅ෦ αʔϏε։ൃ෦ ैۀһ਺ ςΫϊϩδʔຊ෦ ໿໊ αʔϏε։ൃ෦

    ໿໊ ೥݄຤࣌఺ ࣄۀ಺༰ ৽نαʔϏεͷاըɾ։ൃ طଘαʔϏεͷӡ༻ɾอकɾ௥Ճ։ൃ ϛογϣϯ ʮΤϯδχΞϦϯάͷྗͰɺ৽نαʔϏεΛૉૣ͘ఏڙ͢Δʯ
  4. νʔϜߏ੒ ೥݄຤࣌఺ #VTTJOFTT ໊ ❏اը ❏Ӧۀ ❏ࣄۀࢧԉ ❏ϚʔέςΟϯά &OHJOFFS ໊

    ❏ϑϩϯτΤϯυ ❏όοΫΤϯυ ❏Πϯϑϥ ❏σʔλαΠΤϯε %FTJHOFS ໊ ❏6* ❏69 ❏άϥϑΟοΫ
  5. ։ൃϑϩʔ ։ൃ ։ൃ ։ൃ ։ൃ 'SPOUFOE #BDLFOE *OGSBTUSVDUVSF 6*࣮૷ ੩త

    "1*࢓༷࡞੒ Πϯϑϥߏங ϞοΫ"1*ͱ઀ଓ ϞοΫ"1*࡞੒ "1*࣮૷
  6. ϑϩϯτΤϯυͱόοΫΤϯυ෼͔Εͯͷ։ൃ ❏ ݉೚͢Δ͔෼͔ΕΔ͔͸νʔϜʹΑ༷ͬͯʑ ❏ ։ൃ౰ॳ͸ΤϯδχΞ໊͕ମ੍ ❏ ϑϩϯτΤϯυ୲౰ ❏ όοΫΤϯυ୲౰ ❏

    Πϯϑϥ΍ॾʑͷઃܭ౳ 1K. ❏ ͦΕͧΕ͕ಘҙ෼໺Λ୲౰ͯ͠ૉૣ͍։ൃΛ໨ࢦ͢ ❏ ਓ਺͕૿͖͑ͯͨݱࡏ͸ɺδϣϒϩʔςʔγϣϯΛ࣮ࢪ ❏ ఆظతʹ։ൃϝϯόͷϝΠϯͷ࡞ۀྖҬΛม͑Δ ❏ ݸਓͷεΩϧΞοϓ ❏ ݎ࿚ͳνʔϜ࡞ΓɾଐਓԽͷ཈੍
  7. 4BMBSJFTͰͷ"UPNT import { default as MuiButton, ButtonProps } from '@material-

    ui/core/Button'; import { makeStyles } from '@material-ui/core/styles'; interface IButtonProps extends ButtonProps { width?: string; height?: string; } const useStyles = makeStyles({ root: { width: ({ width }: IButtonProps) => width || '', height: ({ height }: IButtonProps) => height || '', } }); export const Button = (props: IButtonProps): JSX.Element => { const styles = useStyles(props); const classes = { root: styles.root }; return ( <MuiButton classes={classes} {...props}> {props.children} </MuiButton> ); }; ❏ "UPNT͸ͦΕҎ্෼ղ͞Εͯ ࢖༻͞ΕΔ͜ͱ͕ͳ͍ίϯϙʔ ωϯτ ❏ .6*Λϥοϓ ❏ NBLF4UZMFTΛ࢖֦ͬͯு ❏ QSPQTͰTUZMFมߋ ❏ 6*ϥΠϒϥϦΛϥοϓ͢Δཧ༝ ❏ ֦ுͰ͖Δ ❏ มߋରԠ࣌ͷӨڹൣғ
  8. ϝϯόͱͷڞ༗ ❏ ࠷৽ΛࣗಈσϓϩΠ ❏ EFWFMPQϒϥϯν΁Ϛʔδ͢Δ࣌ʹ DPNQPOFOUT഑ԼͷϑΝΠϧʹมߋ ͕͋ͬͨ৔߹ʹσϓϩΠΛ࣮ߦ ❏ ߋ৽͞ΕΔͱ4MBDLͰ௨஌ on:

    push: branches: - develop paths: - 'components/**' - name: Slack Notification on SUCCESS if: success() uses: tokorom/action-slack-incoming-webhook@main env: INCOMING_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} with: text: Storyookを更新しました! attachments: | [ { "color": "good", "fields": [ { "title": "Storybook URL", "value": "(Storybookのデプロイ先のURL)" } ] } ]
  9. "1*ͱͷ઀ଓ ❏ ϑϩϯτΤϯυͷ։ൃʹ͸ϞοΫ"1*ͱ઀ଓ͍ͯ͠Δ ❏ ϞοΫ"1*ͱ͸ ❏ ࢼ࡞Ͱ࡞Δ໛ܕͷΑ͏ͳΠϝʔδ ❏ ࣮ࡍͷ%#ͱ͸઀ଓͤͣʹܾ·ͬͨσʔλΛϨεϙϯε ❏

    ZBNMϑΝΠϧΛௐ੔ͯ͠ཉ͍͠σʔλΛϨεϙϯεͰ͖Δ ❏ ϑϩϯτΤϯυ౎߹ͰϨεϙϯεͷσʔλߏ଄Λมߋͯ͠΋Β͍͍ͨΑ͏ͳ৔߹Ͱ΋ ࣮ࡍͷ"1*Λ࣮૷લʹมߋͰ͖Δ ❏ ؀ڥຖʹ"1*ͷ઀ଓઌΛม͍͑ͯΔ fetch(“https://example.com/api/endpoint”)
  10. ؀ڥʹ͍ͭͯ ❏ ϩʔΧϧ؀ڥ ❏ IUUQMPDBMIPTU ❏ ϩʔΧϧͰͷ։ൃ༻ͷ؀ڥ ❏ ϩʔΧϧͰαʔόΛݐͯͯ։ൃ͢Δ ❏

    ։ൃ؀ڥ ❏ IUUQTEFWFYBNQMFDPN ❏ ຊ൪ʹࣅͤͨߏ੒Ͱߏங ❏ ϑϩϯτΤϯυͱόοΫΤϯυ݁߹ ͯ͠ͷಈ࡞֬ೝ ❏ ϏδωεαΠυ΍σβΠφͱͷ࿈ܞ ❏ εςʔδϯά؀ڥ ❏ IUUQTTUHFYBNQMFDPN ❏ ϦϦʔεલͷςετͰ࢖༻ ❏ ຊ൪؀ڥ ❏ IUUQTFYBNQMFDPN ❏ ࣮ࡍʹϦϦʔεɺՔಇ͍ͯ͠Δ؀ڥ
  11. σʔλͷྲྀΕ 4UFQ ϒϥ΢β͔Βೖྗ͞ ΕͨσʔλΛऔಘ ೖྗ ม׵ "1*ίʔϧ ม׵ ඳը 4UFQ

    ೖྗ͞ΕͨσʔλΛ "1*ͷϦΫΤετͷܗ ࣜʹม׵ 4UFQ "1*Λίʔϧ͢Δ 4UFQ "1*͔ΒϨεϙϯε͞ ΕͨσʔλΛ 6*ʹ߹Θͤܗࣜʹม ׵͢Δ 4UFQ ϒϥ΢β΁ඳը͢Δ
  12. σʔλͷྲྀΕ ೖྗ ʙ ม׵ ඳը "1*ίʔϧ ม׵ ೖྗ ม׵ {

    userName: "John", age: "20" } { userName: "John", age: 20, invitationCode: null } 'PSNͷೖྗσʔλ ม׵ޙͷσʔλ
  13. σʔλͷྲྀΕ ೖྗ ʙ ม׵ // 変換 function convertFormToEntity(data: SignupForm): SignupConvertedData

    { const convertedData: SignupConvertedData = { userName: data.userName, age: Number(data.age), invitationCode: data.invitationCode || null }; return convertedData; } // Formのデータの型定義 type SignupForm = { userName: string; age: string; // Formは文字列になる invitationCode?: string; // undefinedまたは文字列 } // リクエストの形式に変換したデータの型定義 type SignupConvertedData = { userName: string; age: number; // 数値に変更 invitationCode: string | null; // 文字列またはnull } ඳը "1*ίʔϧ ม׵ ೖྗ ม׵
  14. σʔλͷྲྀΕ "1*ίʔϧ public async signup(data: SignupConvertedData) { const result =

    await fetch(“/signup”, { method: ‘post’, body: data }); return result; } ඳը "1*ίʔϧ ม׵ ೖྗ ม׵
  15. σʔλͷྲྀΕ ม׵ { userName: "John", createdAt: Thu Oct 28 2021

    18:00:00 GMT+0900 } Ϩεϙϯεͷσʔλ ඳը͢Δσʔλ { userName: "John", createdAt: “2021/10/28 18:00” } ඳը "1*ίʔϧ ม׵ ೖྗ ม׵
  16. ͳͥ͜ΜͳʹͦΕͧΕͷॲཧΛ෼ׂ͍ͯ͠Δͷ͔ 4UFQ ϒϥ΢β͔Βೖྗ͞ ΕͨσʔλΛऔಘ ೖྗ ม׵ "1*ίʔϧ ม׵ ඳը 4UFQ

    ೖྗ͞ΕͨσʔλΛ "1*ͷϦΫΤετͷܗ ࣜʹม׵ 4UFQ "1*Λίʔϧ͢Δ 4UFQ "1*͔ΒϨεϙϯε͞ ΕͨσʔλΛ 6*ʹ߹Θͤܗࣜʹม ׵͢Δ 4UFQ ϒϥ΢β΁ඳը͢Δ
  17. ϩάΠϯͷ৔߹ʹ΋࢖͍ճͤΔ { userName: "John", createdAt: Thu Oct 28 2021 18:00:00

    GMT+0900 } Ϩεϙϯεͷσʔλ ඳը͢Δσʔλ { userName: "John", createdAt: “2021/10/28 18:00” } ඳը "1*ίʔϧ ม׵ ೖྗ ม׵
  18. ͦΕͧΕ۠੾ͬͯςετΛॻ͘͜ͱ͕Ͱ͖Δ 4UFQ ϒϥ΢β͔Βೖྗ͞ ΕͨσʔλΛऔಘ ೖྗ ม׵ "1*ίʔϧ ม׵ ඳը 4UFQ

    ೖྗ͞ΕͨσʔλΛ "1*ͷϦΫΤετͷܗ ࣜʹม׵ 4UFQ "1*Λίʔϧ͢Δ 4UFQ "1*͔ΒϨεϙϯε͞ ΕͨσʔλΛ 6*ʹ߹Θͤܗࣜʹม ׵͢Δ 4UFQ ϒϥ΢β΁ඳը͢Δ ݁߹ςετ σʔλม׵ͷςετ "1*ίʔϧͷςετ σʔλม׵ͷςετ
  19. ϑϩϯτΤϯυύʔτ ·ͱΊ ❏ "UPNJD%FTJOH ❏ খ͍͞ίϯϙʔωϯτͷ߹ମ ❏ มߋʹڧ͍ߏ଄ ❏ 4UPSZCPPL

    ❏ ίϯϙʔωϯτͷΧλϩά ❏ σʔλͷྲྀΕ ❏ ॲཧ͝ͱʹ۠੾Δ ❏ ςετ͸ॏཁ
  20. ࣗݾ঺հ ❏ ஑ా ༟ݾ ❏ ೥݄ ύʔιϧΩϟϦΞೖࣾ ❏ ৬ྺ ❏

    ϕϯνϟʔاۀͰ4&.΍όϦϡʔνΣʔϯվળΛ ߦ͏ɻ ❏ ৽نࣄۀͷاըΛߦ͍ɺاըӡӦΛ͓͜ͳͬͨࣄ ۀΛࢠձࣾԽ͠ࣄۀ੹೚ऀʹɻ ❏ αʔϏεΛΤϯδχΞͱͯࣗ͠෼Ͱ࡞Γ͍ͨࢥ͍ ͔Βݱ৬΁ ❏ ٕज़ ❏ /VYUKT &YQSFTTKT 'BTU"1*
  21. εΩʔϚۦಈ։ൃ ❏ ༻ޠ ❏ εΩʔϚ"1*ͷ࢓༷ ❏ "1*࢓༷ԿΛड͚ͱͬͯԿΛ݁Ռͱͯ͠ฦ͢ͷ͔ΛఆΊͨ΋ͷ ❏ ར఺ ❏

    "1*ͷ࣮૷͕ͳ͍ঢ়ଶͰ΋ϑϩϯτΤϯυ͕։ൃ΍ݕূΛߦ͑Δ "1*࢓༷Λ͸͡Ίʹఆٛ͠ɼ "1*࢓༷ΛݩʹόοΫΤϯυɾϑϩϯτΤϯυ͕ಉ࣌ʹ։ൃΛਐΊ͍ͯ͘
  22. 0QFO"1*ͷϑΥʔϚοτʹैͬͯ࡞੒͞ΕͨZBNM schemes: - "https" - "http" paths: /pet: post: tags:

    - "pet" summary: "Add a new pet to the store" description: "" operationId: "addPet" consumes: - "application/json" - "application/xml" produces: - "application/xml" - "application/json" parameters: - in: "body" name: "body" description: "Pet object that needs to be added to the store" required: true schema: $ref: "#/definitions/Pet" responses: "405": description: "Invalid input" security: - petstore_auth: - "write:pets" - "read:pets"
  23. 0QFO"1* ❏ ར఺ ❏ "1*࢓༷Λڞ௨ͷϑΥʔϚοτͰ؆୯ʹهड़Ͱ͖Δ ❏ (*U౳Λ࢖ͬͯιʔείʔυͱಉ༷ʹ؅ཧ͢Δ͜ͱ͕Ͱ͖Δ ❏ पลπʔϧ͕๛෋ ❏

    ࢓༷Λهࡌ͢Δ͚ͩͰ.PDLαʔό͕࡞ΕΔ ❏ ΠϯλϥΫςΟϒͳ8FCυΩϡϝϯτΛ؆୯ʹ࡞੒͢Δ͜ͱ͕Ͱ͖Δ ❏ εΩʔϚۦಈ։ൃʹγφδʔ͕͋Δ ❏ εΩʔϚ "1*࢓༷ ͷఆٛ ❏ .PDLαʔόͷߏங
  24. SFEPDDMJ ❏ υΩϡϝϯτ࡞੒ ❏ IUNMΛϨϯμϦϯάͯ͘͠ΕΔ $ npx redoc-cli bundle specification.yml

    -o .doc/index.html ❏ υΩϡϝϯταʔόىಈ ❏ "1*%PDVNFOU4FSWFSΛ্ཱͪ͛ͯ͘ΕΔ $ npx redoc-cli server specification.yml
  25. ෼ׂલͷঢ়ଶ paths: /pet/{petId}: get: description: "Returns a single pet" parameters:

    - name: "petId" in: "path" description: "ID of pet to return" type: "integer" responses: "200": description: "successful operation" schema: type: "object" properties: name: type: "string" "1*ͷ1BUI ϝιου ύϥϝʔλ Ϩεϙϯε TQFDJGJDBUJPOZBNM
  26. ෼ׂํ਑ paths: /pet/{petId}: get: description: "Returns a single pet" parameters:

    - name: "petId" in: "path" description: "ID of pet to return" type: "integer" responses: "200": description: "successful operation" schema: type: "object" properties: name: type: "string" TQFDJGJDBUJPOZBNM /api ├── specification.yml ├── models │ └── pet.yml └── paths └── getPet.yml
  27. VTFSQBUITHFU1FUZBNM ❏ QBUITͷதʹ͸"1*͝ͱʹ෼཭͍ͯ͠·͢ ❏ SFGΛ͔ͭͬͯఆٛͨ͠NPEFMΛݺͼग़͠ description: "Returns a single pet"

    parameters: - name: "petId" in: "path" description: "ID of pet to return" type: "integer" responses: "200": description: "successful operation" schema: $ref: "../models/pet.yaml" HFU1FUZBNM
  28. description: "Returns a single pet" parameters: - name: "petId" in:

    "path" description: "ID of pet to return" type: "integer" responses: "200": description: "successful operation" schema: $ref: "../models/pet.yaml" HFU1FUZBNM paths: /pet/{petId}: get: $ref: “./paths/getPet.yaml” TQFDJGJDBUJPOZBNM title: Pet description: Petの情報 type: "object" properties: name: type: "string" QFUZBNM
  29. ֎෦͔Βड͚औͬͨ஋Λ࣮ߦ͢Δ৔߹ const body = JSON.parse(request.body); const addNum = (a: number,

    b: number) => { console.log(a + b); } addNum(body.a, body.b); { "a": "string", "b":2 }
  30. 5ZQF(VBSEΛೖΕΔ͜ͱͰ๷͛Δ const body = JSON.parse(request.body); const addNum = (a: number,

    b: number) => { if (typeof a !== 'number' || typeof b !== 'number') { throw new Error('型違いの値を受け取りました'); } console.log(a + b); } addNum(body.a, body.b);
  31. Ҿ਺͕0CKFDUͷ৔߹ const body = JSON.parse(request.body); interface ISchoolNums { studentNum: number;

    teacherNum: number; } const addObj = (a: ISchoolNums) => { if (!('studentNum' in a) || typeof a.studentNum !== 'number' || !('teacherNum' in a) || typeof a.teacherNum !== 'number') { throw new Error('型違いの値を受け取りました'); } console.log(a.studentNum + a.teacherNum); } addObj(body);
  32. ཧ૝ܗͱͯ͠͸ interface ISchoolNums { studentNum: number; teacherNum: number; } const

    addObj = (a: ISchoolNums) => { if (typeof a !== 'ISchoolNums') { throw new Error('型違いの値を受け取りました'); } console.log(a.studentNum + a.teacherNum); }
  33. 5ZQF(VBSEΛJPUTΛ࢖ͬͯॻ͘ import * as t from 'io-ts' import { isLeft

    } from 'fp-ts/Either'; const SchoolNums = t.type({ studentNum: t.number, teacherNum: t.number }) type ISchoolNums = t.TypeOf<typeof SchoolNums>; const addObj = (a: ISchoolNums) => { const either = SchoolNums.decode(a) if (isLeft(either)) { throw new Error('型違いの値を受け取りま した'); } console.log(a.studentNum + a.teacherNum); } interface ISchoolNums { studentNum: number; teacherNum: number; } const addObj = (a: ISchoolNums) => { if (!('studentNum' in a) || typeof a.studentNum !== 'number' || !('teacherNum' in a) || typeof a.teacherNum !== 'number' ) { throw new Error('型違いの値を受け取りま した'); } console.log(a.studentNum + a.teacherNum); } 5ZQF4DSJQUPOMZ VTFJPUT
  34. ཧ૝ܗͱൺ΂ͯΈΔ const addObj = (a: ISchoolNums) => { const either

    = SchoolNums.decode(a) if (isLeft(either)) { throw new Error('型違いの値を受け取りま した'); } console.log(a.studentNum + a.teacherNum); } ཧ૝ܥ const addObj = (a: ISchoolNums) => { if (typeof a !== 'ISchoolNums') { throw new Error('型違いの値を受け取り ました'); } console.log(a.studentNum + a.teacherNum); } ཧ૝ܥ VTFJPUT
  35. JOUFSGBDF෦෼ import * as t from 'io-ts' import { isLeft

    } from 'fp-ts/Either'; const SchoolNums = t.type({ studentNum: t.number, teacherNum: t.number }) type ISchoolNums = t.TypeOf<typeof SchoolNums>; interface ISchoolNums { studentNum: number; teacherNum: number; } 5ZQF4DSJQUPOMZ VTFJPUT
  36. 5ZQF(VBSEΛJPUTΛ࢖ͬͯॻ͘ ❏ EFDPEFͷฦΓ஋͸&JUIFSܕͱ͍͏-FGUܕ͔3JHIUܕΛ࣋ͪ·͢ ❏ 3JHIUܕͷ৔߹͸ൺֱ͍ͨ͠ܕͱಉ͡Ͱ͋Δ͜ͱΛࣔ͢ ❏ -FGUܕͷ৔߹͸ൺֱ͍ͨ͠ܕͱҧ͏͜ͱΛࣔ͠·͢ export declare type

    Either<E, A> = Left<E> | Right<A>; export interface Left<E> { readonly _tag: 'Left'; readonly left: E; } export interface Right<A> { readonly _tag: 'Right'; readonly right: A; }
  37. 5ZQF(VBSEΛJPUTΛ࢖ͬͯॻ͘ const addObj = (a: ISchoolNums) => { const either

    = SchoolNums.decode(a) if (isLeft(either)) { throw new Error('型違いの値を受け取りま した'); } console.log(a.studentNum + a.teacherNum); } const addObj = (a: ISchoolNums) => { if (!('studentNum' in a) || typeof a.studentNum !== 'number' || !('teacherNum' in a) || typeof a.teacherNum !== 'number' ) { throw new Error('型違いの値を受け取りま した'); } console.log(a.studentNum + a.teacherNum); } 5ZQF4DSJQUPOMZ VTFJPUT
  38. Τϥʔϝοηʔδ ❏ JPUTSFQPSUFSTΛ࢖͏͜ͱͰΤϥʔϝοηʔδΛࣗಈతʹ࡞੒ const SchoolNums = t.type({ studentNum: t.number, teacherNum:

    t.number }) type ISchoolNums = t.TypeOf<typeof SchoolNums>; const addObj = (a: ISchoolNums) => { const either = SchoolNums.decode(a) if (isLeft(either)) { console.log(reporter.report(either)); return; } console.log(a.studentNum + a.teacherNum); }
  39. όοΫΤϯυύʔτ ·ͱΊ ❏ ։ൃϑϩʔ ❏ εΩʔϚۦಈ։ൃΛߦ͏͜ͱͰόοΫΤϯυɾϑϩϯτΤϯυ͕ฏߦͯ͠։ൃ͕ Ͱ͖Δ ❏ 0QFO"1*͸εΩʔϚۦಈ։ൃͱγφδʔ͕ߴ͍ͷͰ͓͢͢ΊͰ͢ ❏

    ඼࣭ ❏ ֎෦ͱͷ઀఺ͰόϦσʔγϣϯΛ͔ͬ͠Γͱߦ͏͜ͱͰαʔϏεͷ඼࣭͕อͯ· ͢ ❏ 5ZQF4DSJQUͰ5ZQF(VBSEॻ͘ͷେมͰ͕͢ɺJPUTΛ࢖͑͹ղܾͰ͖Δ