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. 4BMBSJFTΛࢧ͑ΔνʔϜ։ൃͷݱ৔
    ٕҭ$".1ษڧձ c

    View Slide

  2. *OEFY
    ❏ ಋೖ
    ❏ ॴଐ෦ॺ঺հ
    ❏ 4BMBSJFT঺հ
    ❏ νʔϜ։ൃ
    ❏ 4BMBSJFTνʔϜͷ։ൃମ੍ͳͲͷ঺հ
    ❏ νʔϜ։ൃΛࢧ͑ΔϑϩϯτΤϯυ
    ❏ νʔϜ։ൃΛࢧ͑ΔόοΫΤϯυ

    View Slide

  3. ຊൃදͷ໨ඪ
    ❏ ࣮ࡍͷ։ൃݱ৔ͰͲͷΑ͏ʹνʔϜ։ൃΛߦ͍ͬͯΔͷ͔Πϝʔδ͕Ͱ͖Δ
    ❏ ࢖͍ͬͯΔϥΠϒϥϦʢJPUTͳͲʣͷଘࡏΛ஌ͬͯ΋Β͏
    ❏ ࣋ͪؼͬͯݸਓ։ൃͳͲͰ࢖͏͖͔͚ͬʹͳΔ
    ❏ ࠓޙͷνʔϜ։ൃʹ׆͔ͤΔ
    νʔϜ։ൃͰ࣮ࡍʹ࢖ΘΕ͍ͯΔٕज़΍։ൃख๏ͳͲΛ஌ͬͯ΋Β͏

    View Slide

  4. ࣗݾ঺հ
    ❏ ࠤ઒ ༤ඈ
    ❏ ೥݄ ύʔιϧΩϟϦΞೖࣾ
    ❏ ৬ྺ
    ❏ &$γεςϜͷύοέʔδ։ൃ
    ❏ ҿ৯ళͰͷ଺ࡏ࣌ؒ؅ཧγεςϜ։ൃ
    ❏ ͓ห౰୐഑αʔϏε։ൃ
    ❏ ٕज़
    ❏ /FYUKT /VYUKT 8FC$PNQPOFOUT

    View Slide

  5. αʔϏε։ൃ෦ ֓ཁ
    ॴଐ෦ॺ ςΫϊϩδʔຊ෦ ΤϯδχΞϦϯά౷ׅ෦ αʔϏε։ൃ෦
    ैۀһ਺ ςΫϊϩδʔຊ෦ ໿໊ αʔϏε։ൃ෦ ໿໊ ೥݄຤࣌఺

    ࣄۀ಺༰ ৽نαʔϏεͷاըɾ։ൃ طଘαʔϏεͷӡ༻ɾอकɾ௥Ճ։ൃ
    ϛογϣϯ
    ʮΤϯδχΞϦϯάͷྗͰɺ৽نαʔϏεΛૉૣ͘ఏڙ͢Δʯ

    View Slide

  6. αʔϏεΛಉ࣌։ൃͰ͖Δ૊৫΁

    View Slide

  7. ϦϦʔεࡁΈͷαʔϏε

    View Slide

  8. View Slide

  9. View Slide

  10. View Slide

  11. View Slide

  12. ٕज़ελοΫ
    'SPOUFOE "ENJO
    #BDLFOE *OGSBTUSVDUVSF

    View Slide

  13. νʔϜ։ൃ
    4BMBSJFTνʔϜͷ։ൃ঺հ

    View Slide

  14. νʔϜߏ੒ ೥݄຤࣌఺

    #VTTJOFTT

    ❏اը
    ❏Ӧۀ
    ❏ࣄۀࢧԉ
    ❏ϚʔέςΟϯά
    &OHJOFFS

    ❏ϑϩϯτΤϯυ
    ❏όοΫΤϯυ
    ❏Πϯϑϥ
    ❏σʔλαΠΤϯε
    %FTJHOFS

    ❏6*
    ❏69
    ❏άϥϑΟοΫ

    View Slide

  15. ։ൃϑϩʔ
    #VTJOFTT
    &OHJOFFS
    ཁ݅
    #VTJOFTT
    &OHJOFFS
    %FTJHOFS
    &OHJOFFS &OHJOFFS #VTJOFTT
    &OHJOFFS
    %FTJHOFS
    6*σβΠϯ ٕज़બఆ ։ൃ ϦϦʔε

    View Slide

  16. ։ൃϑϩʔ
    #VTJOFTT
    &OHJOFFS
    ཁ݅
    #VTJOFTT
    &OHJOFFS
    %FTJHOFS
    &OHJOFFS &OHJOFFS #VTJOFTT
    &OHJOFFS
    %FTJHOFS
    6*σβΠϯ ٕज़બఆ ։ൃ ϦϦʔε

    View Slide

  17. ։ൃϑϩʔ ։ൃ
    ։ൃ ։ൃ
    ։ൃ
    'SPOUFOE
    #BDLFOE
    *OGSBTUSVDUVSF
    6*࣮૷ ੩త

    "1*࢓༷࡞੒
    Πϯϑϥߏங
    ϞοΫ"1*ͱ઀ଓ
    ϞοΫ"1*࡞੒ "1*࣮૷

    View Slide

  18. ϑϩϯτΤϯυͱόοΫΤϯυ෼͔Εͯͷ։ൃ
    ❏ ݉೚͢Δ͔෼͔ΕΔ͔͸νʔϜʹΑ༷ͬͯʑ
    ❏ ։ൃ౰ॳ͸ΤϯδχΞ໊͕ମ੍
    ❏ ϑϩϯτΤϯυ୲౰
    ❏ όοΫΤϯυ୲౰
    ❏ Πϯϑϥ΍ॾʑͷઃܭ౳ 1K.

    ❏ ͦΕͧΕ͕ಘҙ෼໺Λ୲౰ͯ͠ૉૣ͍։ൃΛ໨ࢦ͢
    ❏ ਓ਺͕૿͖͑ͯͨݱࡏ͸ɺδϣϒϩʔςʔγϣϯΛ࣮ࢪ
    ❏ ఆظతʹ։ൃϝϯόͷϝΠϯͷ࡞ۀྖҬΛม͑Δ
    ❏ ݸਓͷεΩϧΞοϓ
    ❏ ݎ࿚ͳνʔϜ࡞ΓɾଐਓԽͷ཈੍

    View Slide

  19. ϦϦʔε ˠػೳ௥Ճʹ޲͚ͯ
    ཁ݅ 6*σβΠϯ ٕज़બఆ ։ൃ ϦϦʔε
    ཁ݅ 6*σβΠϯ ٕज़બఆ ։ൃ ϦϦʔε

    View Slide

  20. ϑϩϯτΤϯυ
    νʔϜ։ൃͰͷϑϩϯτΤϯυ঺հ
    ❏ "UPNJD%FTJHO
    ❏ 4UPSZCPPL
    ❏ "1*ͱͷ઀ଓ
    ❏ σʔλͷྲྀΕͱςετ

    View Slide

  21. "UPNJD%FTJHO
    Ҿ༻ IUUQTBUPNJDEFTJHOCSBEGSPTUDPNDIBQUFS
    ❏ ཁૉʹ෼͚ͯͦΕΒΛ૊Έ߹Θ࣮ͤͯ૷͢Δ6*σβΠϯͷख๏
    ❏ খ͍͞ίϯϙʔωϯτͷ߹ମ
    ❏ "UPNT͔Βॱʹ࣮૷͢Δ
    ❏ มߋ͕ଟ͘ͳΓ͕ͪͳ6*࣮૷ΛͰ͖ΔݶΓมߋʹڧ͍ߏ଄Ͱ࣮૷͍ͨ͠

    View Slide

  22. "UPNJD%FTJHOྫ

    View Slide

  23. 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 (

    {props.children}

    );
    };
    ❏ "UPNT͸ͦΕҎ্෼ղ͞Εͯ
    ࢖༻͞ΕΔ͜ͱ͕ͳ͍ίϯϙʔ
    ωϯτ
    ❏ .6*Λϥοϓ
    ❏ NBLF4UZMFTΛ࢖֦ͬͯு
    ❏ QSPQTͰTUZMFมߋ
    ❏ 6*ϥΠϒϥϦΛϥοϓ͢Δཧ༝
    ❏ ֦ுͰ͖Δ
    ❏ มߋରԠ࣌ͷӨڹൣғ

    View Slide

  24. νʔϜ։ൃͰ͸ଞͷϝϯό͕͢Ͱʹ
    ࣮૷ࡁͷίϯϙʔωϯτ͕͋Δ͔΋͠Εͳ͍
    ࣮૷ͨ͠ίϯϙʔωϯτҰཡ͕Έ͍ͨʂ

    View Slide

  25. View Slide

  26. 4UPSZCPPL "UPNT

    View Slide

  27. 4UPSZCPPL 0SHBOJTNT

    View Slide

  28. 4UPSZCPPL
    ❏ ࢖͍͍ͨίϯϙʔωϯτ͕͢Ͱʹଘࡏ͢Δ͔Λ୳͠΍͘͢ͳΔ
    ❏ σβΠφͱͷೝࣝ߹ΘͤͳͲʹ΋࢖͑Δ
    ❏ ΧϥʔύϨοτ
    ❏ ࠷ۙͰ͸4UPSZCPPLͱ'JHNBͰ੔߹ੑΛऔΔྫ΋͋Δ

    View Slide

  29. ϝϯόͱͷڞ༗
    ❏ ࠷৽ΛࣗಈσϓϩΠ
    ❏ EFWFMPQϒϥϯν΁Ϛʔδ͢Δ࣌ʹ
    DPNQPOFOUT഑ԼͷϑΝΠϧʹมߋ
    ͕͋ͬͨ৔߹ʹσϓϩΠΛ࣮ߦ
    ❏ ߋ৽͞ΕΔͱ4MBDLͰ௨஌
    on:
    push:
    branches:
    - develop
    paths:
    - 'components/**'
    - name: Slack Notification on SUCCESS
    if: success()
    uses: tokorom/[email protected]
    env:
    INCOMING_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
    with:
    text: Storyookを更新しました!
    attachments: |
    [
    {
    "color": "good",
    "fields": [
    {
    "title": "Storybook URL",
    "value": "(Storybookのデプロイ先のURL)"
    }
    ]
    }
    ]

    View Slide

  30. "1*ͱͷ઀ଓ

    View Slide

  31. "1*ͱͷ઀ଓ
    ❏ ϑϩϯτΤϯυͷ։ൃʹ͸ϞοΫ"1*ͱ઀ଓ͍ͯ͠Δ
    ❏ ϞοΫ"1*ͱ͸
    ❏ ࢼ࡞Ͱ࡞Δ໛ܕͷΑ͏ͳΠϝʔδ
    ❏ ࣮ࡍͷ%#ͱ͸઀ଓͤͣʹܾ·ͬͨσʔλΛϨεϙϯε
    ❏ ZBNMϑΝΠϧΛௐ੔ͯ͠ཉ͍͠σʔλΛϨεϙϯεͰ͖Δ
    ❏ ϑϩϯτΤϯυ౎߹ͰϨεϙϯεͷσʔλߏ଄Λมߋͯ͠΋Β͍͍ͨΑ͏ͳ৔߹Ͱ΋
    ࣮ࡍͷ"1*Λ࣮૷લʹมߋͰ͖Δ
    ❏ ؀ڥຖʹ"1*ͷ઀ଓઌΛม͍͑ͯΔ
    fetch(“https://example.com/api/endpoint”)

    View Slide

  32. ؀ڥʹ͍ͭͯ
    ❏ ϩʔΧϧ؀ڥ
    ❏ IUUQMPDBMIPTU
    ❏ ϩʔΧϧͰͷ։ൃ༻ͷ؀ڥ
    ❏ ϩʔΧϧͰαʔόΛݐͯͯ։ൃ͢Δ
    ❏ ։ൃ؀ڥ
    ❏ IUUQTEFWFYBNQMFDPN
    ❏ ຊ൪ʹࣅͤͨߏ੒Ͱߏங
    ❏ ϑϩϯτΤϯυͱόοΫΤϯυ݁߹
    ͯ͠ͷಈ࡞֬ೝ
    ❏ ϏδωεαΠυ΍σβΠφͱͷ࿈ܞ
    ❏ εςʔδϯά؀ڥ
    ❏ IUUQTTUHFYBNQMFDPN
    ❏ ϦϦʔεલͷςετͰ࢖༻
    ❏ ຊ൪؀ڥ
    ❏ IUUQTFYBNQMFDPN
    ❏ ࣮ࡍʹϦϦʔεɺՔಇ͍ͯ͠Δ؀ڥ

    View Slide

  33. ؀ڥຖʹ"1*ͷ઀ଓઌΛมߋ͢Δ
    ❏ FOWϑΝΠϧΛ࡞੒ͯ͠؀ڥม਺Λ༻ҙ͢Δ
    ❏ ϩʔΧϧ؀ڥͰ͸ϞοΫ"1*ɺ։ൃ؀ڥ͸։ൃ؀ڥͷ"1*
    ❏ ϩʔΧϧ؀ڥͰ͸ FOWMPDBMɺ։ൃ؀ڥͰ͸FOWEFWͳͲ؀ڥઃఆϑΝΠϧΛ෼͚Δ
    API_HOST=http://0.0.0.0:4010
    PORT=3000
    API_HOST=https://dev.example.com/api
    PORT=8080
    .env.local .env.dev

    View Slide

  34. σʔλͷྲྀΕ

    View Slide

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

    View Slide

  36. σʔλͷྲྀΕ ೖྗ ʙ ม׵
    ඳը
    "1*ίʔϧ ม׵
    ೖྗ ม׵
    {
    userName: "John",
    age: "20"
    }
    {
    userName: "John",
    age: 20,
    invitationCode: null
    }
    'PSNͷೖྗσʔλ ม׵ޙͷσʔλ

    View Slide

  37. σʔλͷྲྀΕ ೖྗ ʙ ม׵
    // 変換
    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*ίʔϧ ม׵
    ೖྗ ม׵

    View Slide

  38. σʔλͷྲྀΕ "1*ίʔϧ
    public async signup(data: SignupConvertedData) {
    const result = await fetch(“/signup”, {
    method: ‘post’, body: data
    });
    return result;
    }
    ඳը
    "1*ίʔϧ ม׵
    ೖྗ ม׵

    View Slide

  39. σʔλͷྲྀΕ ม׵
    {
    userName: "John",
    createdAt: Thu Oct 28 2021 18:00:00 GMT+0900
    }
    Ϩεϙϯεͷσʔλ ඳը͢Δσʔλ
    {
    userName: "John",
    createdAt: “2021/10/28 18:00”
    }
    ඳը
    "1*ίʔϧ ม׵
    ೖྗ ม׵

    View Slide

  40. ͳͥ͜ΜͳʹͦΕͧΕͷॲཧΛ෼ׂ͍ͯ͠Δͷ͔
    4UFQ
    ϒϥ΢β͔Βೖྗ͞
    ΕͨσʔλΛऔಘ
    ೖྗ ม׵ "1*ίʔϧ ม׵ ඳը
    4UFQ
    ೖྗ͞ΕͨσʔλΛ
    "1*ͷϦΫΤετͷܗ
    ࣜʹม׵
    4UFQ
    "1*Λίʔϧ͢Δ
    4UFQ
    "1*͔ΒϨεϙϯε͞
    ΕͨσʔλΛ
    6*ʹ߹Θͤܗࣜʹม
    ׵͢Δ
    4UFQ
    ϒϥ΢β΁ඳը͢Δ

    View Slide

  41. ൚༻తʹ࢖͏͜ͱ͕Ͱ͖
    ࢓༷มߋʹ΋ରԠ͠΍͘͢ͳΔ

    View Slide

  42. ϩάΠϯͷ৔߹ʹ΋࢖͍ճͤΔ
    {
    userName: "John",
    createdAt: Thu Oct 28 2021 18:00:00 GMT+0900
    }
    Ϩεϙϯεͷσʔλ ඳը͢Δσʔλ
    {
    userName: "John",
    createdAt: “2021/10/28 18:00”
    }
    ඳը
    "1*ίʔϧ ม׵
    ೖྗ ม׵

    View Slide

  43. ͦΕͧΕ۠੾ͬͯςετΛॻ͘͜ͱ͕Ͱ͖Δ
    4UFQ
    ϒϥ΢β͔Βೖྗ͞
    ΕͨσʔλΛऔಘ
    ೖྗ ม׵ "1*ίʔϧ ม׵ ඳը
    4UFQ
    ೖྗ͞ΕͨσʔλΛ
    "1*ͷϦΫΤετͷܗ
    ࣜʹม׵
    4UFQ
    "1*Λίʔϧ͢Δ
    4UFQ
    "1*͔ΒϨεϙϯε͞
    ΕͨσʔλΛ
    6*ʹ߹Θͤܗࣜʹม
    ׵͢Δ
    4UFQ
    ϒϥ΢β΁ඳը͢Δ
    ݁߹ςετ
    σʔλม׵ͷςετ "1*ίʔϧͷςετ σʔλม׵ͷςετ

    View Slide

  44. ςετͷॏཁੑ
    ❏ ෆ۩߹ͷૣظൃݟ
    ❏ Өڹൣғͷߟྀ࿙ΕͰ༧ظͤ͵ՕॴͰͷෆ۩߹ʹ΋ؾͮ͘͜ͱ͕Ͱ͖Δ
    ❏ ෳࡶͳॲཧͰ΋ςετ͕ॻ͔Εͯ͋Δͱཧղ͠΍͘͢ͳΔ
    ❏ ʮςετ͸ཪ੾Βͳ͍ʯ

    View Slide

  45. ϑϩϯτΤϯυύʔτ ·ͱΊ
    ❏ "UPNJD%FTJOH
    ❏ খ͍͞ίϯϙʔωϯτͷ߹ମ
    ❏ มߋʹڧ͍ߏ଄
    ❏ 4UPSZCPPL
    ❏ ίϯϙʔωϯτͷΧλϩά
    ❏ σʔλͷྲྀΕ
    ❏ ॲཧ͝ͱʹ۠੾Δ
    ❏ ςετ͸ॏཁ

    View Slide

  46. όοΫΤϯυ
    νʔϜ։ൃͰͷόοΫΤϯυ঺հ
    ❏ "1*࢓༷
    ❏ 0QFO"1*
    ❏ 5ZQF(VBSE
    ❏ JPUT

    View Slide

  47. ࣗݾ঺հ
    ❏ ஑ా ༟ݾ
    ❏ ೥݄ ύʔιϧΩϟϦΞೖࣾ
    ❏ ৬ྺ
    ❏ ϕϯνϟʔاۀͰ4&.΍όϦϡʔνΣʔϯվળΛ
    ߦ͏ɻ
    ❏ ৽نࣄۀͷاըΛߦ͍ɺاըӡӦΛ͓͜ͳͬͨࣄ
    ۀΛࢠձࣾԽ͠ࣄۀ੹೚ऀʹɻ
    ❏ αʔϏεΛΤϯδχΞͱͯࣗ͠෼Ͱ࡞Γ͍ͨࢥ͍
    ͔Βݱ৬΁
    ❏ ٕज़
    ❏ /VYUKT &YQSFTTKT 'BTU"1*

    View Slide

  48. όοΫΤϯυͰࠓ೔͓࿩͢͠Δ͜ͱͷςʔϚ
    ϑϩϯτΤϯυͱͷڠྗ

    View Slide

  49. ΤϯδχΞ͕ҙࣝ͠΍͍͢
    ։ൃεϐʔυ ։ൃϑϩʔ
    ΞϓϦέʔγϣϯ
    ͷ඼࣭
    όϦσʔγϣϯ

    View Slide

  50. ։ൃϑϩʔ

    View Slide

  51. Ұൠతͳ։ൃϑϩʔ
    όοΫΤϯυ ϑϩϯτΤϯυ
    ࣮૷
    ࣮૷
    ϦϦʔε
    ࣮૷͕ਐ·ͳ͍

    View Slide

  52. εΩʔϚۦಈ։ൃ
    ❏ ༻ޠ
    ❏ εΩʔϚ"1*ͷ࢓༷
    ❏ "1*࢓༷ԿΛड͚ͱͬͯԿΛ݁Ռͱͯ͠ฦ͢ͷ͔ΛఆΊͨ΋ͷ
    ❏ ར఺
    ❏ "1*ͷ࣮૷͕ͳ͍ঢ়ଶͰ΋ϑϩϯτΤϯυ͕։ൃ΍ݕূΛߦ͑Δ
    "1*࢓༷Λ͸͡Ίʹఆٛ͠ɼ
    "1*࢓༷ΛݩʹόοΫΤϯυɾϑϩϯτΤϯυ͕ಉ࣌ʹ։ൃΛਐΊ͍ͯ͘

    View Slide

  53. εΩʔϚۦಈ։ൃ
    όοΫΤϯυ ϑϩϯτΤϯυ
    "1*࢓༷࡞੒
    ࣮૷
    ϦϦʔε
    ࣮૷
    "1*࢓༷Λݩʹ
    ϞοΫαʔόΛͨͯΔ

    View Slide

  54. εΩʔϚۦಈ։ൃ
    όοΫΤϯυ ϑϩϯτΤϯυ
    "1*࢓༷࡞੒
    ࣮૷
    ϦϦʔε
    ࣮૷
    "1*࢓༷Λݩʹ
    ϞοΫαʔόΛͨͯΔ

    View Slide

  55. εΩʔϚۦಈ։ൃͰόοΫΤϯυ։ൃऀ͕ߦ͍ͬͯΔ͜ͱ
    "1*࢓༷࡞੒
    "1*࢓༷Λݩʹ
    ϞοΫαʔόΛͨͯΔ
    ❏ "1*࢓༷ͷ࡞੒
    ❏ "1*࢓༷Λਓ͕ཧղ͠΍͍͢ܗʹϑΥʔϚοτ͢Δ
    ❏ "1*࢓༷ΛݩʹϞοΫαʔόΛߏங

    View Slide

  56. 0QFO"1*
    ❏ 0QFO"1*ͱ͸
    ❏ 3&45GVM"1*ͷυΩϡϝϯτΛهड़͢ΔͨΊͷϑΥʔϚοτ࢓༷
    "1*࢓༷Λ౷Ұͨ͠ܗࣜͰهࡌ͢Δ͜ͱ͕Ͱ͖Δ

    View Slide

  57. 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"

    View Slide

  58. ͜ͷ··Ͱ͸ಡΈʹ͍͘
    0QFO"1*पลπʔϧͷ4XBHHFS6*Λ࢖͏ͱ

    View Slide

  59. ಡΈ΍͘͢ͳΔʂ

    View Slide

  60. 0QFO"1*
    ❏ ར఺
    ❏ "1*࢓༷Λڞ௨ͷϑΥʔϚοτͰ؆୯ʹهड़Ͱ͖Δ
    ❏ (*U౳Λ࢖ͬͯιʔείʔυͱಉ༷ʹ؅ཧ͢Δ͜ͱ͕Ͱ͖Δ
    ❏ पลπʔϧ͕๛෋
    ❏ ࢓༷Λهࡌ͢Δ͚ͩͰ.PDLαʔό͕࡞ΕΔ
    ❏ ΠϯλϥΫςΟϒͳ8FCυΩϡϝϯτΛ؆୯ʹ࡞੒͢Δ͜ͱ͕Ͱ͖Δ
    ❏ εΩʔϚۦಈ։ൃʹγφδʔ͕͋Δ
    ❏ εΩʔϚ "1*࢓༷
    ͷఆٛ
    ❏ .PDLαʔόͷߏங

    View Slide

  61. 4BMBSJFTͰͷ0QFO"1*ΞʔΩςΫνϟ
    ❏ )5.-ͱͯ͠ϨϯμϦϯά
    ❏ .PDLαʔόͷߏங

    View Slide

  62. (FOFSBUF)5.-
    ❏ ਓ͕ݟ΍͍͢Α͏ʹ0QFO"1*ϑΥʔϚοτͷϑΝΠϧ͔Β)5.-Λੜ੒
    ❏ 4XBHHFS6*΍3F%PDͱ͍ͬͨπʔϧΛ׆༻͢Δ͜ͱ͕ଟ͍
    4XBHHFS6* 3F%PD

    View Slide

  63. (JU)VCUSFOET
    ϖΠϯͰͷݟ΍͢͞΍NBSLEPXOΛಡΈࠐΊΔ͜ͱͳͲ͔Β3F%PDΛ࠾༻

    View Slide

  64. SFEPDDMJ
    ❏ υΩϡϝϯτ࡞੒
    ❏ IUNMΛϨϯμϦϯάͯ͘͠ΕΔ
    $ npx redoc-cli bundle specification.yml -o .doc/index.html
    ❏ υΩϡϝϯταʔόىಈ
    ❏ "1*%PDVNFOU4FSWFSΛ্ཱͪ͛ͯ͘ΕΔ
    $ npx redoc-cli server specification.yml

    View Slide

  65. .PDL4FSWFS
    ❏ 1SJTN
    ❏ 0QFO"1*ϑΝΠϧΛݩʹϑϩϯτΤϯυ͔Β0QFO"1*Ͱఆٛͨ͠ϞοΫαʔό
    ΛͨͯΔ͜ͱ͕Ͱ͖Δ
    ❏ 0QFO"1*ϑΝΠϧΛهड़ͯ͠EPDLFSDPNQPTFZBNMΛ࡞੒
    ❏ EPDLFSDPNQPTFVQίϚϯυΛ࢖͏ͱ.PDLαʔό্ཱ͕͕ͪΔ
    version: '3'
    services:
    api-mock:
    image: stoplight/prism
    volumes:
    - .:/api
    command: >
    mock -h 0.0.0.0 /api/specification.yml
    ports:
    - 4010:4010

    View Slide

  66. ͜͜·Ͱ
    ❏ εΩʔϚۦಈ։ൃΛ׆༻͢ΔͱϑϩϯτΤϯυͱόοΫΤϯυͷ࣮૷Λฏߦͯ͢͢͠
    ΊΔ͜ͱ͕Ͱ͖ɺ։ൃ޻਺ͷ࡟ݮͰ͖Δ
    ❏ εΩʔϚۦಈ։ൃ͸0QFO"1*΍ͦͷपลπʔϧΛ࢖͏͜ͱͰ0QFO"1*ϑΝΠϧΛ࡞੒
    ͢Δ͚ͩͰऔΓೖΕΔ͜ͱ͕Ͱ͖·͢

    View Slide

  67. 4BMBSJFTͰͷ0QFO"1*ϑΝΠϧͷѻ͍͔ͨ
    ❏ ϑΝΠϧ෼ׂ
    ❏ 0QFO"1*ͷ࢓༷ࣗମʹ͸ϑΝΠϧ෼ׂͷ࢓༷͸ଘࡏ͠·ͤΜ
    ❏ ZBNMͷ࢓༷ʹଇΓϑΝΠϧ෼ׂΛ͍ͯ͠·͢
    ❏ ෼཭ͨ͠ϑΝΠϧ͸ SFGͰࢀর
    0QFO"1*ϑΝΠϧ͸ංେԽ͕ͪ͠

    View Slide

  68. ෼ׂલͷঢ়ଶ 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

    View Slide

  69. ෼ׂํ਑ 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

    View Slide

  70. VTFSNPEFMTQFUZBNM
    ❏ NPEFMT͸"1*ͷϨεϙϯε΍ϦΫΤετ
    ͷܕΛఆٛ͢ΔϑΝΠϧ͕ଘࡏ͢Δ
    title: Pet
    description: Petの情報
    type: "object"
    properties:
    name:
    type: "string"
    QFUZBNM

    View Slide

  71. 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

    View Slide

  72. TQFDJGJDBUJPOZBNM
    ❏ QBUITΛ"1*͝ͱʹݺͼग़͢
    paths:
    /pet/{petId}:
    get:
    $ref: “./paths/getPet.yaml”
    TQFDJGJDBUJPOZBNM

    View Slide

  73. 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

    View Slide

  74. όϦσʔγϣϯ
    ˞࢖༻ݴޠ͸5ZQF4DSJQUΛ૝ఆ͍ͯ͠·͢

    View Slide

  75. ͪ͜Β͸ίϯύΠϥʔʹ௨ΔͰ͠ΐ͏͔
    const addNum = (a: number, b: number) => {
    console.log(a + b);
    }
    addNum('a', 2);

    View Slide

  76. ֎෦͔Βड͚औͬͨ஋Λ࣮ߦ͢Δ৔߹
    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
    }

    View Slide

  77. 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);

    View Slide

  78. Ҿ਺͕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);

    View Slide

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

    View Slide

  80. 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.TypeOfSchoolNums>;
    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

    View Slide

  81. ཧ૝ܗͱൺ΂ͯΈΔ
    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

    View Slide

  82. 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.TypeOfSchoolNums>;
    interface ISchoolNums {
    studentNum: number;
    teacherNum: number;
    }
    5ZQF4DSJQUPOMZ VTFJPUT

    View Slide

  83. 5ZQF(VBSEΛJPUTΛ࢖ͬͯॻ͘
    ❏ EFDPEFͷฦΓ஋͸&JUIFSܕͱ͍͏-FGUܕ͔3JHIUܕΛ࣋ͪ·͢
    ❏ 3JHIUܕͷ৔߹͸ൺֱ͍ͨ͠ܕͱಉ͡Ͱ͋Δ͜ͱΛࣔ͢
    ❏ -FGUܕͷ৔߹͸ൺֱ͍ͨ͠ܕͱҧ͏͜ͱΛࣔ͠·͢
    export declare type Either = Left | Right;
    export interface Left {
    readonly _tag: 'Left';
    readonly left: E;
    }
    export interface Right {
    readonly _tag: 'Right';
    readonly right: A;
    }

    View Slide

  84. 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

    View Slide

  85. Τϥʔϝοηʔδ
    ❏ JPUTSFQPSUFSTΛ࢖͏͜ͱͰΤϥʔϝοηʔδΛࣗಈతʹ࡞੒
    const SchoolNums = t.type({
    studentNum: t.number,
    teacherNum: t.number
    })
    type ISchoolNums = t.TypeOf;
    const addObj = (a: ISchoolNums) => {
    const either = SchoolNums.decode(a)
    if (isLeft(either)) {
    console.log(reporter.report(either));
    return;
    }
    console.log(a.studentNum + a.teacherNum);
    }

    View Slide

  86. Τϥʔϩά
    ❏ SFQPSUΛ࢖͏͜ͱͰԿ͕ͲͷΑ͏ʹؒҧ͍ͬͯΔ͔؆୯ʹϝοηʔδΛ࡞੒Ͱ͖Δ
    ❏ ೔ຊޠ΁ͷม׵΍ϝοηʔδͷܗࣜΛΧελϚΠζ͢Δ͜ͱ΋Ͱ͖Δ
    Expecting number at studentNum but instead got: "30"

    View Slide

  87. JPUTͷར༻Օॴ
    ❏ JPUTΛ࢖ͬͨόϦσʔγϣϯ͸֎෦͔ΒσʔλΛड͚औΔ৔߹ʹ࢖༻
    ❏ "1*͕ड͚औͬͨϦΫΤετ
    ❏ %#͔Βऔಘͨ͠஋
    αʔϏε͸ϑϩϯτΤϯυͱόοΫΤϯυͳͲ͕ϝοηʔδΛૹΓ͋͏͜ͱͰ੒Γཱͬͯ
    ͍·͢ɻ
    ͦΕͧΕͷΞϓϦέʔγϣϯίʔυͷதͰ֎෦ͱͷ઀఺෦෼ΛόϦσʔγϣϯ͢Δ͜ͱͰ
    αʔϏεͷ඼࣭Λอͭ͜ͱ͕Ͱ͖·͢ɻ

    View Slide

  88. όοΫΤϯυύʔτ ·ͱΊ
    ❏ ։ൃϑϩʔ
    ❏ εΩʔϚۦಈ։ൃΛߦ͏͜ͱͰόοΫΤϯυɾϑϩϯτΤϯυ͕ฏߦͯ͠։ൃ͕
    Ͱ͖Δ
    ❏ 0QFO"1*͸εΩʔϚۦಈ։ൃͱγφδʔ͕ߴ͍ͷͰ͓͢͢ΊͰ͢
    ❏ ඼࣭
    ❏ ֎෦ͱͷ઀఺ͰόϦσʔγϣϯΛ͔ͬ͠Γͱߦ͏͜ͱͰαʔϏεͷ඼࣭͕อͯ·
    ͢
    ❏ 5ZQF4DSJQUͰ5ZQF(VBSEॻ͘ͷେมͰ͕͢ɺJPUTΛ࢖͑͹ղܾͰ͖Δ

    View Slide

  89. お知らせ

    View Slide

  90. ຊબߟ͕։͍࢝ͯ͠·͢ʂ ೥݄຤࣌఺Ͱͷ৘ใ

    ❏ ΤϯδχΞίʔεʢ˞ಛఆͷϙδγϣϯ֬໿Ͱ഑ଐʣ
    ■エンジニア
    ・サービス企画開発本部:新規サービスの開発に上流の企画段階から参画するエンジニア
    ・事業戦略本部:「EPEB」などの大規模プロダクトの開発を担当するエンジニア
    ■データサイエンティスト
    ・テクノロジー本部:最新技術を活用しながら、データドリブンで新規事業開発を行うデータサイエンティスト
    ・事業戦略本部:主に「EPEB」に蓄積された個人・法人データを分析し事業に還元するデータサイエンティスト
    ■*5企画
    事業目標達成に向けた*5化の促進を行うために*5戦略の立案から企画・構想、プロジェクトマネジメン
    トを担当

    View Slide

  91. 5FDIϝσΟΞ
    ΈΜͳͷʮ͸ͨΒ͘ʯΛςοΫͰͭ͘Δ
    ࣾ಺ͷςΫϊϩδʔ׆༻ࣄྫ΍ࣾһΠϯλϏϡʔΛൃ৴ʂ
    *5ɾςΫϊϩδʔਓࡐͷͨΊͷࣾձਓίϛϡχςΟɻษڧ
    ձ৘ใ΍ɺΠϕϯτϨϙʔτΛൃ৴ʂ

    View Slide

  92. ΫϨδοτ
    *MMVTUSBUJPOCZ4UPSZTFU IUUQTTUPSZTFUDPN

    View Slide

  93. ͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ

    View Slide