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

フロントエンドのパラダイムを参考にバックエンド開発を再考する / TypeScript による GraphQL バックエンド開発

Naoya Ito
October 01, 2022

フロントエンドのパラダイムを参考にバックエンド開発を再考する / TypeScript による GraphQL バックエンド開発

2022年10月1日に開催された #postdev での発表です

Naoya Ito

October 01, 2022
Tweet

More Decks by Naoya Ito

Other Decks in Technology

Transcript

  1. ϑϩϯτΤϯυͷύϥμΠϜΛࢀߟʹόοΫΤϯυ։ൃΛ࠶ߟ͢Δ
    5ZQF4DSJQUʹΑΔ (SBQI2-όοΫΤϯυ։ൃ
    גࣜձࣾ Ұٳ
    ҏ౻ ௚໵

    View Slide

  2. K2VFSZظ
    .1"XJUIK2VFSZ
    ʮ+BWB4DSJQU͕ಘҙʯͳਓ͸͍͕ͨɺϑϩϯτΤϯυ όοΫΤϯυͱ͍͏໾ׂ෼୺͸ߦΘ
    Εͣɺશһ͕ಉ͡ྖҬΛ୲౰͍ͯͨ͠

    View Slide

  3. 7VFKTಋೖظ
    .1"
    7VFϑϩϯτΤϯυ
    ʮϑϩϯτΤϯυΤϯδχΞʯʮόοΫΤϯυΤϯδχΞʯͱ͍͏໾ׂ෼୺͕গͣͭ͠ͳ͞Ε
    ΔΑ͏ʹ

    View Slide

  4. /VYU࣌୅
    /VYU
    (SBQI2-όοΫΤϯυ
    ʮϑϩϯτΤϯυʯʮόοΫΤϯυʯͷ໾ׂ෼୺͕໌֬ʹ
    ϑϩϯτΤϯυͷΈ։ൃΛ͢Δɺͱ͍͏୲౰ऀ΋

    View Slide

  5. ૒ํͷٕज़తؔ৺ࣄʹΪϟοϓ
    • ΞϓϦέʔγϣϯͷঢ়ଶ؅ཧϞσϧ
    • σβΠϯγεςϜ
    • ϓϦϨϯμϦϯά
    • ŋŋŋ
    ϑϩϯτΤϯυ
    όοΫΤϯυ
    • υϝΠϯϞσϧ
    • ϨΠϠʔυɾΞʔΩςΫνϟ
    • $234
    • ŋŋŋ
    ૊৫ͷٕज़࿅౓্͕͕Ε͹্͕Δ΄Ͳɺؔ৺ࣄͷΪϟοϓ͕޿͕͍ͬͯͬͨ

    View Slide

  6. ͖͔͚ͬ
    • ৽نϓϩδΣΫτ্ཱ͕͕ͪΔ
    • ϑϩϯτΤϯυ͸ 3FBDU3FMBZ3FDPJM Λ࠾༻
    • (SBQI2-όοΫΤϯυ ŋŋŋ ௕Β͘ 1ZUIPOͰɺΫϥεΛଟ༻ͨ͠ΫϦʔϯΞʔΩςΫνϟ
    తͳઃܭͰ΍͖͕ͬͯͨɺࠓճ͸Ͳ͏͢Δ͔

    View Slide

  7. ৽نϓϩμΫτ͸খ͞ͳνʔϜͰີ౓ߴٞ͘࿦ͯ͠࡞Γ͍ͨ
    • ސ٬ͷͲΜͳ໰୊ΛɺͲ͏ղܾ͍͔ͨ͠ ŋŋŋ σΟεΧογϣϯΛେࣄʹ͍ͨ͠
    – ϑϩϯΤϯυɺόοΫΤϯυؔ܎ͳ͘ର৅υϝΠϯྖҬʹৄ͘͠ͳΓ͍ͨ
    – ͲΜͳମݧΛɺͲΜͳ 6*ͰɺͲ͏͍͏ϞσϧͰɺͲΜͳσʔλઃܭΛ࣮ͯ͠ݱ͢Δͷ͔ɻॳظ
    ϑΣʔζͰ͸શһ͕ͳΔ΂͘޿͘஌͓͖͍ͬͯͨ
    • ૣظʹ໾ׂ෼୺Λߦ͍͗͢Δͱɺؔ৺ࣄͷ෼அΛ༠ൃͯ͠͠·͏

    View Slide

  8. 3FBDUͰϑϩϯτΤϯυΛ։ൃ͔ͯ͠Βɺ όοΫΤϯυΛॻ͘ͱŋŋŋ
    • 3FBDUŋŋŋ খ͞ͳؔ਺Λ૊Έ߹Θͤͯએݴతʹॻ͍͍ͯ͘
    • όοΫΤϯυ ŋŋŋ ΫϥεΛͨ͘͞Μॻ͍ͯɺϨΠϠʔΛލ͙ͱ %50Ͱͷ஋٧Ίସ͑Λߦͬ
    ͯɺJOUFSGBDFͰґଘੑͷٯసΛߦͬͯŋŋŋ
    – ʮŋŋŋϑϩϯτΤϯυͩͱ͜͏͍͏͜ͱɺ͋Μ·Γ΍Βͳ͍ΑͶʯ
    ։ൃ࣌ͷϝϯλϧϞσϧͷΪϟοϓ͕େ͖͍
    ίϯςΩετεΠονͷෛ୲΋େ͖͍

    View Slide

  9. όοΫΤϯυ։ൃͷ΍ΓํΛ࠶ߟͯ͠Έ͍ͨ
    • 3FBDUΛ࢖͍ͬͯΔͱϑϩϯτΤϯυ͸ബ͘ॻ͘͜ͱ͕Ͱ͖Δ
    • ؔ৺ࣄ͕ҧ͏ͷ͸౰વɻ͔ͱ͍ͬͯɺ΍Γํ͕ҧ͏ͷΛશٙ͘Θͳ͍ͷ΋Ͳ͏ͩΖ͏
    ϑϩϯτΤϯυͷঢ়ଶ؅ཧ͸ෳࡶ
    ͦͷෳࡶͳ΋ͷΛͲ͏ѻ͏͔ɺݱ࣌఺Ͱ࠷ྑͷϞσϧͷͻͱ͕ͭ 3FBDUͷ͸ͣ
    ʮෳࡶͳঢ়ଶΛͲ͏ѻ͏͔ʯͱ͍͏؍఺ͰɺαʔόʔαΠυ΋ಉ͡Α͏ʹߟ͑ΒΕͳ͍ͷ͔
    Α͠ɺ(SBQI2-όοΫΤϯ
    υ΋ 5ZQF4DSJQUͰॻ͍ͯΈ
    Α͏

    View Slide

  10. վΊͯɺࡢࠓͷϑϩϯτΤϯυͷϓϩάϥϛϯάύϥμΠϜΛߟ͑ͯΈΔ
    https://zenn.dev/mizchi/articles/oop-think-modern

    View Slide

  11. &MNΞʔΩςΫνϟ
    https://guide.elm-lang.jp/architecture/

    View Slide

  12. update : Msg -> Model -> ( Model, Cmd Msg )
    update msg model =
    case msg of
    ToggleLike ->
    ( { model | photo = Maybe.map toggleLike model.photo }, Cmd.none )
    UpdateComment comment ->
    ( { model | photo = Maybe.map (updateComment comment) model.photo }, Cmd.none )
    SaveComment ->
    ( { model | photo = Maybe.map saveNewComment model.photo }, Cmd.none )
    LoadFeed (Ok photo) ->
    ( { model | photo = Just photo }, Cmd.none )
    LoadFeed (Err _) ->
    ( model, Cmd.none )
    viewLikeButton : Photo -> Html Msg
    viewLikeButton model =
    let buttonClass = if model.liked then ...
    div [ class "like-button" ]
    [ i [ class "fa fa-2x", class buttonClass, onClick ToggleLike ] [] ]
    &MN
    7JFX͸ .PEFMΛඳըɻ
    Ϣʔβʔૢ࡞ʹԠͯ͡Πϕ
    ϯτΛૹΔͱŋŋŋ
    &MNϥϯλΠϜ͕ VQEBUFؔ਺
    ΛݺͿɻؔ਺ʹ͸Πϕϯτͷछ
    ྨʹԠͨ͡Ϟσϧͷঢ়ଶભҠΛ
    هड़͓ͯ͘͠

    View Slide

  13. ঢ়ଶભҠͷؔ਺
    ֎քͱ΍Γͱ
    Γ *0

    イベント
    コマンド

    View Slide

  14. model -> model'
    model -> model' model -> model'
    ΠϕϯτΛܖػʹঢ়ଶ͕ભҠ͢Δ ŋŋŋ ࣌ܥྻʹج͍ͮͨঢ়ଶ

    View Slide

  15. ঢ়ଶભҠͷؔ਺ ϥϯλΠϜ΍ϑ
    ϨʔϜϫʔΫ
    イベント
    コマンド
    Πϕϯτʹ൐͍ঢ়ଶΛભҠͤͯ͞ɺ͋ͱ͸ϑϨʔϜϫʔΫ΍ϥϯλΠϜʹ೚ͤΔ

    View Slide

  16. 3FEVY"QQMJDBUJPO%BUB'MPX
    https://redux.js.org/tutorials/essentials/part-1-overview-concepts

    View Slide

  17. 3FDPJM
    function TextInput() {
    const [text, setText] = useRecoilState(textState);
    const onChange = (event) => {
    setText(event.target.value);
    };
    return (




    Echo: {text}

    );
    }

    View Slide

  18. 3FDPJM
    • 3FEVYʹΑΔେ͖ͳάϩʔόϧεςʔτ͸ɺѻ͍ͮΒ͍ہ໘΋͋ͬͨ
    • ΑΓείʔϓΛڱ͘ɺখ͘͞ɺϩʔΧϧεςʔτ VTF4UBUF
    ಉ༷ʹϑοΫͰએݴతʹѻ͍
    ͍ͨ
    &MNΞʔΩςΫνϟ΍ 3FEVYͰൃݟ͞Εͨྑ͍ϓϥΫςΟε͸౿ऻͭͭ͠ɺͰ͖Δݶ
    Γখ͞ͳείʔϓͰঢ়ଶΛѻ͍͚ͬͯΔͱྑͦ͞͏

    View Slide

  19. View Slide

  20. όοΫΤϯυͰ΋ಉ͡Α͏ʹʮ࣌ܥྻʹجͮ͘ঢ়ଶભҠʯͷࢹ఺Ͱߟ͑ΒΕͳ͍͔
    • όοΫΤϯυͷੈքͷओͳʮঢ়ଶʯ ŋŋŋ υϝΠϯϞσϧͷঢ়ଶ
    • υϝΠϯϞσϧͷঢ়ଶΛભҠͤ͞ΔΠϕϯτ ŋŋŋ υϝΠϯΠϕϯτ

    View Slide

  21. ͨͱ͑͹ʮ॓ധ༧໿ʯΛྫʹυϝΠϯϞσϧΛվΊͯߟ͑ͯΈΔ
    • ͲΜͳ؍఺ʹ஫໨ͯ͠ߟ͑ͯΈΔ΂͖͔
    – σʔλߏ଄
    – &3ਤ
    – Ϋϥεͷ࣮૷
    – ը໘
    • ͍ͣΕ΋੩తͳߏ଄ʹয఺Λ౰͍ͯͯΔɻࢹ఺Λม͑ͯΈ͍ͨ
    – ಈతͳ΋ͷŋŋŋυϝΠϯΠϕϯτ΍ঢ়ଶʹয఺Λ౰ͯͯΈΔͱ

    View Slide

  22. ʮ༧໿ʯͷঢ়ଶભҠʹண໨ͯ͠ΈΔ
    ༧໿׬ྃ
    Χʔυܾࡁ
    ࡁΈ
    Ωϟϯηϧ
    ॓ധࡁΈ

    View Slide

  23. ৽ن༧໿͕׬ྃ͢Δલ͔ΒυϝΠϯϞσϧ͸ଘࡏ͍ͯ͠Δ
    ༧໿׬ྃ
    Ωϟϯηϧ
    ॓ധࡁΈ
    ೖྗ
    ݕূࡁΈ
    ೖྗະݕূ
    ࡏݿ֬อ
    ࡁΈ

    View Slide

  24. ঢ়ଶ͸Կ͔͠ΒͷΠϕϯτΛܖػʹભҠ͢Δ
    ༧໿׬ྃ
    Ωϟϯηϧ
    ॓ധࡁΈ
    ೖྗ
    ݕূࡁΈ
    ೖྗະݕূ
    ࡏݿ֬อ
    ࡁΈ
    ༧໿Λ։࢝ͨ͠ ݕূ͕׬ྃͨ͠ ࡏݿΛ֬อͨ͠ ༧໿Λߦͬͨ
    Ωϟϯηϧ͞Εͨ
    ॓ധͨ͠

    View Slide

  25. model -> model'
    model -> model' model -> model'
    ͓΍

    View Slide

  26. ྆୺͸֎෦ͱͷΠϯλϑΣʔε
    model -> model
    model -> model
    &WFOU)BOEMFS
    8FC"QQͳΒ
    SPVUFS

    %#ʹอଘ͠
    Ϩεϙϯε
    6*

    View Slide

  27. model -> model
    model -> model
    event
    &WFOU)BOEMFS
    8FC"QQͳΒ
    SPVUFS

    %#ʹอଘ͠
    Ϩεϙϯε
    6*
    event
    event
    ֎ͷੈք
    ֎ͷੈք
    ֎ͷੈք
    Πϕϯτ ˠϞσϧͷঢ়ଶભҠ
    🤔 Ͳ͔͜Ͱݟͨͳŋŋŋ

    View Slide

  28. ঢ়ଶભҠͷؔ਺ ϥϯλΠϜ΍ϑ
    ϨʔϜϫʔΫ
    イベント
    コマンド
    ಉ͡

    View Slide

  29. *0ঢ়ଶભҠ *0
    Pure function
    Model -> Model
    *0
    JOQVUMPBE

    *0
    PVUQVU

    View Slide

  30. View Slide

  31. https://www.slideshare.net/ScottWlaschin/reinventing-the-transaction-script-ndc-london-2020

    View Slide

  32. ϑϩϯτΤϯυͱόοΫΤϯυͷঢ়ଶ؅ཧ
    • ϑϩϯτΤϯυͷঢ়ଶ؅ཧ ŋŋŋ ओͳؔ৺ࣄ͸ʮΞϓϦέʔγϣϯͷঢ়ଶʯ
    • όοΫΤϯυͷঢ়ଶ؅ཧ ŋŋŋ ओͳؔ৺ࣄ͸ʮυϝΠϯϞσϧɺυϝΠϯΦϒδΣΫτͷঢ়
    ଶʯ
    ؅ཧ͍ͯ͠Δঢ়ଶͷίϯςΩετ͸ҧ͏΋ͷͷ
    ঢ়ଶ؅ཧͷϞσϧ͸ࣅͨΑ͏ʹߟ͑ΒΕΔͷͰ͸ͳ͍͔

    View Slide

  33. ʮυϝΠϯΦϒδΣΫτͷঢ়ଶભҠΛએݴతʹهड़ͭͭ͠ *0͔Β෼཭͢Δʯ
    • ͜ͷίϯηϓτͰ࣮૷
    • &MNΞʔΩςΫνϟ͓Αͼ '%%%ຊΛࢀߟʹ

    View Slide

  34. ͜ͷελΠϧͰΑ͘࢖͏΋ͷ
    • type / interface
    • λά෇͖ϢχΦϯ ௚࿨ܕ

    • 3FTVMUܕ
    • ΧϦʔԽ
    • ܕͷϒϥϯυԽ ίϯύχΦϯΦϒδΣΫτ

    View Slide

  35. ͋·Γ࢖Θͳ͍΋ͷ
    • class
    • ྫ֎ͷ throw
    – Error Ϋϥε͸࢖͍·͢

    View Slide

  36. ؆୯ͳϢʔεέʔεྫ

    View Slide

  37. 5BHΤϯςΟςΟ ू໿ϧʔτ
    ͷঢ়ଶભҠʹண໨͢Δ
    7BMJEBUFE
    6OWBMJEBUFE $SFBUFE
    ೖྗ͕͋ͬͨ ݕূͨ͠ ࡞੒ͨ͠
    ˞͜͜Ͱͷʮ$SFBUFEʯ͸͋͘·ͰυϝΠϯΦϒδΣΫτ͕
    l࡞੒ࡁΈzʹͳͬͨঢ়ଶͰ͋ͬͯɺσʔλϕʔεʹϨίʔυ
    Λ௥Ճͨ͠ɺͱ͍͏ঢ়ଶͰ͸ͳ͍

    View Slide

  38. 5BHΫϥεΛ࡞Δ
    export class Tag {
    state: 'Unvalidated' | 'Validated' | 'Created',
    id: TagId | undefined,
    groupId: RestaurantGroupId,
    label: string,
    icon: TagIcon | undefined,
    sortOrder: number | undefined,
    builtin: boolean | undefined
    }

    View Slide

  39. export class Tag {
    state: 'Unvalidated' | 'Validated' | 'Created',
    id: TagId | undefined,
    groupId: RestaurantGroupId,
    label: string,
    icon: TagIcon | undefined,
    sortOrder: number | undefined,
    builtin: boolean | undefined
    }
    ঢ়ଶભҠલʹ͸֬ఆ͠ͳ͍ϓϩύςΟ͕ VOEFGJOFE ʹͳͬͯ͠·͏ŋŋŋ

    View Slide

  40. interface UnvalidatedTag {
    kind: 'Unvalidated'
    groupId: string
    label: string
    icon?: { symbol: string; type: TagIconType; color?: string | null | undefined } | null | undefined
    }
    interface ValidatedTag {
    kind: 'Validated'
    groupId: RestaurantGroupId
    label: string
    icon: TagIcon
    }
    export interface CreatedTag {
    kind: 'Created'
    id: TagId
    groupId: RestaurantGroupId
    label: TagLabel
    icon: TagIcon
    sortOrder: number
    builtin: boolean
    }
    //※この型は実際には出番がないので使っていない
    export type Tag = UnvalidatedTag | ValidatedTag | CreatedTag
    ͦ͜Ͱঢ়ଶ͝ͱʹܕΛఆٛ͢Δ
    ঢ়ଶ͕ભҠ͢Δ υϝΠϯΠϕ
    ϯτ͕ൃੜ͢Δ͝ͱʹϞσϧͷ஋
    ͕֬ఆ͍ͯ͘͠ͷ͕એݴͰ͖͍ͯ
    Δ

    View Slide

  41. l.BLF*MMFHBM4UBUFT6OSFQSFTFOUBCMFz
    interface User {
    memberId: MemberId | undefined
    guestId: GuestId | undefined
    }
    interface Member {
    userId: MemberId
    }
    interface Guest {
    guestId: GuestId
    }
    type User = Member | Guest
    औΓಘΔ஋ͷछྨ਺͸֤ଐੑͷੵʹͳΔ ௚ੵ

    Y
    ɾ྆ํ VOEFGJOFE
    ɾ྆ํͷ஋͕ຒ·Δ
    ͱ͍͏࢓্༷͋Γಘͳ͍ঢ়ଶ͕ੜ·ΕΔ
    औΓಘΔछྨ਺͸֤ଐੑͷ࿨ ௚࿨


    ࢓্༷͋Γಘͳ͍ঢ়ଶ͸දݱ͠ͳ͍
    Ϩίʔυ͸ʮ͔ͭ "/%
    ʯ ϢχΦϯ͸ʮ·ͨ͸ 03
    ʯ

    View Slide

  42. ͪ͜ΒΑΓ΋ŋŋŋ
    export class Tag {
    state: 'Unvalidated' | 'Validated' | 'Created',
    id: TagId | undefined,
    groupId: RestaurantGroupId,
    label: string,
    icon: TagIcon | undefined,
    sortOrder: number | undefined,
    builtin: boolean | undefined
    }

    View Slide

  43. interface UnvalidatedTag {
    kind: 'Unvalidated'
    groupId: string
    label: string
    icon?: { symbol: string; type: TagIconType; color?: string | null | undefined } | null | undefined
    }
    interface ValidatedTag {
    kind: 'Validated'
    groupId: RestaurantGroupId
    label: string
    icon: TagIcon
    }
    export interface CreatedTag {
    kind: 'Created'
    id: TagId
    groupId: RestaurantGroupId
    label: TagLabel
    icon: TagIcon
    sortOrder: number
    builtin: boolean
    }
    export type Tag = UnvalidatedTag | ValidatedTag | CreatedTag
    ͪ͜Βͷํ͕ɺ஋ͷ૊Έ߹Θͤύλʔϯ͕গͳ͘ݫີ

    View Slide

  44. type validateTag = (model: UnvalidatedTag) => ValidatedTag
    const validateTag: validateTag = (model) => {
    // (省略: 値の validation ...)
    return {
    ...model,
    kind: 'Validated',
    groupId: RestaurantGroupId(model.groupId),
    icon: model.icon ? TagIcon(model.icon) : NoIcon(),
    }
    }
    ঢ়ଶΛભҠͤ͞Δεςοϓ ؔ਺

    View Slide

  45. ঢ়ଶΛભҠͤ͞Δεςοϓ ؔ਺

    type createTag = (model: ValidatedTag) => CreatedTag
    const createTag: CreatedTag = (model) => {
    return {
    ...model,
    kind: 'Created',
    id: generateTagId(),
    sortOrder: getTagSortOrder({ groupId: model.groupId }),
    builtin: false,
    }
    }
    ४උ͕੔ͬͯॳΊͯ஋͕֬ఆ
    ͢ΔͷΛࣗવʹهड़Ͱ͖Δ
    ͳ͓ getTagSortOrder ͸ *0͕͋Δ
    ͨΊ %*͍ͯ͠Δɻࠓճ͸ׂѪ

    View Slide

  46. Ϟσϧͷܕɺؔ਺ͷܕʹΑͬͯঢ়ଶભҠΛએݴతʹهड़͢Δ
    7BMJEBUFE
    6OWBMJEBUFE $SFBUFE
    (model: UnvalidatedTag) => ValidatedTag (model: ValidatedTag) => CreatedTag

    View Slide

  47. ݸผʹఆٛͨ͠ঢ়ଶભҠͷؔ਺Λܨ͍͛ͨ
    • Ͱ΋ɺܭࢉ͸ʮ్தͰࣦഊ͢ΔʯՄೳੑ͕͋Δ
    – ͨͱ͑͹υϝΠϯϞσϧͷࣄલ৚݅Λຬͨ͞ͳ͍Τϥʔ
    – 7BMJEBUJPO&SSPSŋŋŋ
    – .BY5BH-JNJU&YDFFEFEʜ
    • ʮ్தͰࣦഊ͢Δʯ͜ͱΛܕͰએݴͰ͖ͳ͍͔ ˠ 3FTVMUܕ

    View Slide

  48. 3FTVMUܕͰࣦഊͷՄೳੑͷ͋ΔܭࢉΛҰຊಓʹ߹੒͢Δ
    import { Result, ok, err } from 'neverthrow'
    function itsUnder100(n: number): Result {
    return n <= 100 ? ok(n) : err(new Error('100より大きい数字です'))
    }
    function itsEven(n: number): Result {
    return n % 2 == 0 ? ok(n) : err(new Error('奇数です'))
    }
    function itsPositive(n: number): Result {
    return n > 0 ? ok(n) : err(new Error('負数です'))
    }
    const result = ok(96).andThen(itsUnder100).andThen(itsEven).andThen(itsPositive)
    result.match(
    (n) => console.log(n),
    (error) => { throw error }
    )

    View Slide

  49. 3FTVMUܕͰঢ়ଶભҠؔ਺Λͭͳ͛ͯɺҰͭͷʮϫʔΫϑϩʔʯΛ࡞Δ
    7BMJEBUFE5BH

    $SFBUFE5BH
    6OWBMJEBUFE5BH

    7BMJEBUFE5BH
    8PSL'MPX

    View Slide

  50. type validateTag = (model: UnvalidatedTag) => Result
    const validateTag: validateTag = (model) => {
    const groupId = RestaurantGroupId(model.groupId)
    const label = TagLabel(model.label)
    const icon = model.icon ? TagIcon(model.icon) : ok(NoIcon())
    const values = Result.combine(tuple(groupId, label, icon))
    return values.map(([groupId, label, icon]) => ({
    ...model,
    kind: 'Validated',
    groupId,
    label,
    icon,
    }))
    }
    ঢ়ଶભҠͷ੒ޭ ࣦഊΛ 3FTVMUܕͰฦ͢Α͏ʹ͢Δ
    ஋ͷੜ੒ʹࣦഊ͢ΔՄೳੑ͕͋ΔͷͰɺ
    ͜ΕΒ΋ 3FTVMUܕΛฦ͢

    View Slide

  51. 3FTVMUܕͰঢ়ଶભҠؔ਺Λܨ͛ͯɺϫʔΫϑϩʔ υϝΠϯϩδοΫ
    Λ࡞Δ
    type WorkFlow = (model: UnvalidatedTag) => Result
    export const createTagWorkFlow: WorkFlow = (model) =>
    ok(model).andThen(validateTag).andThen(createTag)

    View Slide

  52. ϫʔΫϑϩʔͷ࢝·ΓͱऴΘΓ͕ɺ֎քͱͷ JOPVU
    7BMJEBUFE5BH

    $SFBUFE5BH
    6OWBMJEBUFE5BH

    7BMJEBUFE5BH
    8PSL'MPX
    ೖྗͷ%50
    ྫ(SBQI2-*OQVU5ZQF
    Λ
    6OWBMJEBUFE5BHʹม׵
    UBH3FQPTJUPSZͰ
    $SFBUFE5BHΛอଘ

    View Slide

  53. (SBQI2-SFTPMWFS3FQPTJUPSZ %#
    ͱϫʔΫϑϩʔΛ઀ଓ͢Δ
    import { saveCreatedTag } from '../../../customers/repos/tagRepository'
    export const createTagMutation = mutationField('createTag', {
    ...
    resolve(_root, { input }, context) {
    const workflow = createTagWorkFlow()
    // GraphQL 入力をワークフローの入力に変換
    const unvalidatedTag = toUnvalidatedTag({
    ...input,
    groupId: context.operator.groupId,
    })
    // ワークフローを実行し Repository パターンの関数で DB に保存 (ここも Result で繋ぐ)
    const result = ok(unvalidatedTag).andThen(workflow).andThen(saveCreatedTag(context))
    return result.match(
    (tag) => ({
    tag: {
    ...tag,
    id: toGlobalId('Tag', tag.id),
    },
    }),
    (error) => {
    // ここで初めて例外をスロー (単に Nexus にエラーを伝える手段としてスローする)
    throw error
    }
    )
    },
    })

    View Slide

  54. ok(model).andThen(workflow).andThen(saveCreatedTag(context))
    (SBQI2-*OQVU
    σʔλϕʔε
    Pure function
    Model -> Model
    *0
    JOQVUMPBE

    *0
    PVUQVU

    View Slide

  55. ঢ়ଶભҠͷؔ਺ ϥϯλΠϜ΍ϑ
    ϨʔϜϫʔΫ
    イベント
    コマンド

    View Slide

  56. σʔλϑϩʔϓϩάϥϛϯά
    • 3FTVMUܕͰࣦഊͷ͋ΔܭࢉΛ߹੒͠ɺσʔλͷ௨Γಓͱͯ͠ͷܭࢉաఔΛ࡞Δ
    – ͦ͜ʹσʔλΛ์ΓࠐΉͱɺͦͷதΛ௨ͬͯঢ়ଶભҠͨ͠σʔλ͕ಘΒΕΔ
    – σʔλΛσʔλͷ··ɺͦͷՄൖੑΛԼ͛ͣʹѻ͍͍ͨɻ݁Ռ class ͷొ৔ػձ͕ͳ͍
    • ܭࢉΛҰຊಓʹ͢Δ
    – େҬ୤ग़͸͠ͳ͍ɻେҬ୤ग़͢Δͱܭࢉ͕ҰຊಓʹͳΒͳ͍ ˠྫ֎Λ࢖Θͳ͍
    – ࣦഊͷ෼ذ͸ 3FTVMUͰ߹੒ ˞3FTVMUܕ͸Ϟφυ

    – ܭࢉ͕ҰຊಓʹͳΔ σʔλ͸ෆมɻೝ஌ෛՙ͕௿͘ͳΔ

    View Slide

  57. ϑϩϯτΤϯυͱͷൺֱ
    • ࣌ܥྻʹجͮ͘ঢ়ଶભҠ σʔλϑϩʔ
    Λએݴతʹهड़͢Δɺͱ͍͏ߟ͑ํ͸ಉ͡ʹͳͬͨ
    – ܕͱখ͞ͳؔ਺ͷએݴతͳهड़ͰɺϑϩʔΛ૊Έ্͛Δ
    • ҰํɺϫʔΫϑϩʔͷ࣮૷Λ͍ͯ͠Δͱ͖ͷײ֮ʹ͸·ͩڑ཭͕͋Δ
    – υϝΠϯΠϕϯτͰঢ়ଶભҠɺͱ͍ͬͯ΋ଟ͘ͷ৔߹͸ʮ7BMJEBUFͯ͠ɺೖྗͰυϝΠϯϞσϧ
    Λߋ৽͢Δʯ͚ͩ
    • ݁ՌɺϫʔΫϑϩʔ͸ఆܕతͳهड़͕ଟ͘ͳΔ ŋŋŋ ϑϨʔϜϫʔΫԽͰ͖Δ͔΋
    • ϑϩϯτΤϯυ͸ͦ͜Λ 3FBDU΍ &MNͳͲͷϑϨʔϜϫʔΫ͕΍͍ͬͯΔ ͔ͩΒɺΠϕϯτʹର͢ΔϞ
    σϧͷঢ়ଶભҠͱɺͦͷঢ়ଶΛදݱ͢ΔϓϨθϯςʔγϣϯͷهड़ʹूதͰ͖Δ

    • ΠϕϯτͱΠϕϯτͷͭͳ͗߹Θͤ ྫ3FTVMUܕʹΑΔ߹੒
    Λࣗ෼Ͱهड़͍ͯ͠Δͷ͕ݱঢ়

    View Slide

  58. ͕࣌ؒແ͍ͷͰɺ࣮૷ͷৄࡉ͸·ͨޙ೔

    • ͜ͷ··Ͱ͸τϥϯβΫγϣϯεΫϦϓτͰɺڽूੑ͕௿͘ͳͬͯ͠·͏
    • ΤϯςΟςΟܕͷपลʹɺίΞυϝΠϯϩδοΫͷؔ਺Λ࣮૷ͯ͠Ϟδϡʔϧ෼ׂ͢Δ
    • ϫʔΫϑϩʔ͸ͦͷίΞυϝΠϯϩδοΫΛ࢖ͬͯϑϩʔΛ૊ΈཱͯΔ໾ׂ͚ͩʹͳΔ
    – ΫϦʔϯɾΞʔΩςΫνϟͷ 6TF$BTFͱಉ͡

    View Slide

  59. ैདྷख๏ʹൺֱ͠هड़ྔ͸গͳ͍
    • σʔλΛσʔλͷ··ӡΜͰ͍ΔͷͰʮ٧Ίସ͑ͯ໾ׂͷҟͳΔผछͷΦϒδΣΫτʯʹ͢
    Δŋŋŋͱ͍͏ඞཁ͕ͳ͍
    – ஋ͷίϐʔ͸࣮ࡍʹ͸ͱ͜ΖͲ͜Ζ΍͍ͬͯΔ͕ ͨͩͷσʔλΛ
    ෼ׂ୅ೖͰهड़Ͱ͖ΔͷͰɺ
    هड़ྔ͸࠷খ

    View Slide

  60. ݱ࣌఺Ͱͷײ૝
    • ࢓্༷ͳ͍ঢ়ଶΛ࡞ΒͣʹࡁΉͨΊɺݎ࿚
    • ΑΓෳࡶͳϫʔΫϑϩʔΛ࣮૷ͨ͠৔߹΋ಉ͡ߏ଄ʹऩ·Δɻೝ஌ෛՙ͕௿͍
    • 3FTVMUܕʹΑΓܭࢉΛܨ͛ΒΕΔΑ͏هड़͢Δྑ͍ڧ੍ྗ͕ಇ͘
    – ͨͩ͠ andThen().andThen().asyncAndThen.map() ͸͕͢͞ʹಡΈͮΒ͍
    – )BTLFMMͷ EPه๏ɺ'ͷίϯϐϡςʔγϣϯࣜʹ૬౰͢Δ΋ͷ͕ཉ͍͠ŋŋŋ
    • ஋ͷ٧Ίସ͑ͷهड़͕ͳ͍ͷ͸ ͱͯ΋
    خ͍͠

    View Slide

  61. ;Γ͔͑Γ
    https://zenn.dev/mizchi/articles/oop-think-modern

    View Slide

  62. ·ͱΊ
    • ࣌ܥྻʹجͮ͘ঢ়ଶભҠͷએݴͱϑϨʔϜϫʔΫଆʹΑΔঢ়ଶભҠɺௐఀ
    – ϑϩϯτΤϯυ͸ɺͦͷͨΊͷϑϨʔϜϫʔΫͷ࣮૷͕ॆ࣮͍ͯ͠Δ
    • ʮએݴతϓϩάϥϛϯάʯͷϓϥΫςΟε͸ɺ೥ݱ࣌఺Ͱ͸ܦݧతʹ΋ྑ͍΋ͷ
    – όοΫΤϯυ։ൃ΋͜ͷߟ͑ํʹऩᏑ͍ͤͯ͘͞ͷ΋ѱ͘ͳ͍ͷͰ͸ ˠ΍ͬͯΈͨΒ޷ײ৮
    – ྲྀߦΔ͔Ͳ͏͔͸Θ͔Γ·ͤΜ
    • ϑϩϯτΤϯυ όοΫΤϯυͷύϥμΠϜΪϟοϓΛগͳ͍͖͍ͯͨ͘͠

    View Slide

  63. ัଊ
    • 5ZQF4DSJQUʹ૊ΈࠐΈͷ 3FTVMUܕ͸ͳ͍ͷͰɺOFWFSUISPXΛ࢖ͬͨɻଞʹ΋ GQUTͳͲͷީิ͕͋Δ
    • 3FTVMUܕ͸ XPSLGMPXͷߏ੒͚ͩͰͳ͋͘ΒΏΔ৔ॴͰ࢖͏
    • Ұํʮ3FTVMUܕύζϧʯʹ͸·Δ͕࣌͋Δŋŋŋ
    • ঺հ࢓੾Εͳ͔͕ͬͨ 1SPNJTF΋ 3FTVMU"TZODʹΑͬͯ 3FTVMUԽͯ͠߹੒Ͱ͖Δ
    • %PNBJO.PEFMJOH.BEF'VODUJPOBM ʹ͍ͭͯ
    – ॻ੶ͷఏҊख๏ͦͷ··ͩͱτϥϯβΫγϣϯεΫϦϓτʹͳΓڽूੑ͕௿͘ͳΔɻ ͨͩ͠ *0͸͖ͪΜͱ෼཭͞Ε͍ͯΔͷͰɺΑ
    Γྑ͍τϥϯβΫγϣϯεΫϦϓτͩͱ͸ࢥ͏
    ΤϯςΟςΟͷܕͷपΓʹυϝΠϯϩδοΫͷؔ਺ΛूΊΔ͜ͱͰͦͷ໰୊͸ղফ
    Ͱ͖͍ͯΔ
    – ͋ΒΏΔϓϩύςΟʹܕΛఆٛ͢ΔυϝΠϯϓϦϛςΟϒతͳख๏Λ࠾͍ͬͯΔ͕ɺͦ͜͸࠾༻͠ͳ͔ͬͨɻ7BMVF0CKFDUͷΈ
    /PNJOBMͳܕఆٛΛܕͷϒϥϯυԽ ˞ΦϥΠϦʔͷ 5ZQF4DSJQUຊࢀর
    ʹΑͬͯߦ͍ͬͯΔ
    – 3FQPTJUPSZ͸ඞཁͳ͍ͱॻ͔Ε͍͕ͯͨɺू໿Λ͔ͤͬ͘ߏ੒͍ͯ͠ΔͷͰैདྷ௨Γ 3FQPTJUPSZύλʔϯͰू໿୯ҐͰͷӬଓԽ
    Λߦ͍ͬͯΔɻͳ͓ɺ3FQPTJUPSZͷத਎͸ 1SJTNBΛ࢖͍ͬͯΔ
    • UZQFͱ JOUFSGBDFͷ࢖͍෼͚ ŋŋŋ GQUTʹ฿͍ͬͯΔɻಛʹͦ͏͠ͳ͚Ε͹ͳΒ͍ཧ༝͸ͳ͍ͱࢥ͏
    • ؔ਺͕σϑΥϧτͰΧϦʔԽ͞Εͳ͍ͷ͸࢓ํ͕ͳ͍ɻ࣌ંΧϦʔԽͨ͠ͷΛ๨Εͯ͸·Δ
    • SFBEPOMZ͸ԣணͯ͠ɺ࢖ͬͯͳ͍ɻ ͪΌΜͱݕ౼͍ͯ͠ͳ͍

    View Slide

  64. ࢀߟจݙ
    • 4DPUU8MBTDIJO ʮ%PNBJO.PEFMJOH.BEF'VODUJPOBMᴷ5BDLMF4PGUXBSF$PNQMFYJUZXJUI%PNBJO%SJWFO
    %FTJHOBOE'ʯ
    • +FSFNZ'BJSCBOL ஶ
    ϠΪͷ͘͞ΒͪΌΜ ຋༁
    ʮϓϩάϥϛϯά&MNᴷ҆શͰϝϯςφϯε͠΍͍͢ϑϩϯτΤϯυ
    ΞϓϦέʔγϣϯ։ൃೖ໳ʯ
    • ླ໦ ྅ଠ ʮϓϩΛ໨ࢦ͢ਓͷͨΊͷ5ZQF4DSJQUೖ໳ ᴷ ҆શͳίʔυͷॻ͖ํ͔Βߴ౓ͳܕͷ࢖͍ํ·Ͱʯ
    • #PSJT$IFSOZ ஶ
    ࠓଜ ݠ࢜ ؂म
    ݪ ོจ ຋༁
    ʮϓϩάϥϛϯά5ZQF4DSJQUʕεέʔϧ͢Δ+BWB4DSJQUΞϓϦέʔ
    γϣϯ։ൃʯ
    • ௚ੵܕͱ௚࿨ܕʹ͍ͭͯ
    – ʮू߹ͱͯ͠ͷܕ u"O*OUSPEVDUJPOUP&MNʯ IUUQTHVJEFFMNMBOHKQBQQFOEJYUZQFT@BT@TFUTIUNM
    – ʮͳͥ࣍ʹֶͿݴޠ͸ؔ਺ܕͰ͋Δ΂͖͔ʯ IUUQTZNPUPOHQPPIBUFOBCMPHDPNFOUSZ

    View Slide