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

TypeScript による GraphQL バックエンド開発

Naoya Ito
October 14, 2022

TypeScript による GraphQL バックエンド開発

10/14 の Tech Play での発表資料です
https://techplay.jp/event/873259

Naoya Ito

October 14, 2022
Tweet

More Decks by Naoya Ito

Other Decks in Technology

Transcript

  1. 5ZQF4DSJQUʹΑΔ (SBQI2-όοΫΤϯυ։ൃ
    5ZQF4DSJQUͷܕγεςϜͱσʔλϑϩʔʹண໨ͨ͠એݴతϓϩάϥϛϯά
    גࣜձࣾ Ұٳ
    ҏ౻ ௚໵

    View Slide

  2. Ϟνϕʔγϣϯ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  6. ຊ࿦ʹೖΔલʹɺ(SBQI2-όοΫΤϯυͱ $234

    View Slide

  7. $234
    • ίϚϯυΫΤϦ੹຿෼཭ݪଇ $PNNBOE2VFSZ3FTQPOTJCJMJUZ4FHSFHBUJPO$234

    – ΞϓϦέʔγϣϯ࣮૷ͷจ຺Ͱ͸
    ࢀরܥ 2VFSZ
    ͱߋ৽ܥ $PNNBOE
    ͰҟͳΔϞσϧΛ࢖
    ͏Ξϓϩʔν

    View Slide

  8. (SBQI2-2VFSZͱυϝΠϯϞσϧͷʮू໿ʯ͸ɺטΈ߹Θ͕ͤѱ͍
    • (SBQI2-2VFSZ ˞ .VUBUJPOͰ͸ͳ͍

    – ΫϥΠΞϯτ͕ϢʔεέʔεΛߏ੒͢Δ
    – ू໿୯ҐͰ͸ͳ͘ɺϦιʔε୯ҐͰૢ࡞͞ΕΔ
    • (SBQI2-2VFSZʹݶΒͣ
    ू໿Λશ෦Ҿ͘Θ͚ʹ͍͔ͳ͍৔໘ͰͲ͏͢Δ͔ŋŋŋ͸ '"2
    – ྫݕࡧҰཡϖʔδ
    – $234Λద༻ͯ͠ 2VFSZ4FSWJDFΛ࣮૷͢Δͱ͍͏ͷ͕ɺͻͱͭͷղܾࡦ

    View Slide

  9. (SBQI2-2VFSZͰ͸ෳࡶͳۀ຿ϩδοΫ͕͋·Γඞཁͳ͍ ˞զʑͷ৔߹

    • ܦݧతʹ෼͔͖ͬͯͨ
    – ଟ͘͸σʔλϕʔεͷ஋Λͦͷ·· (SBQI2-0CKFDUʹ͢Ε͹ྑ͍͚ͩ
    – ϦονͳʮυϝΠϯϞσϧʯ͸ࢀরܥʹ͸ɺඞཁͳ͍͜ͱ͕ଟ͍
    • ͦ΋ͦ΋ %%%ͷू໿͸ࢀরͱ͍͏ΑΓɺू໿શମͷ੔߹ੑΛอͭ͜ͱʹॏ͖͕ஔ͔Ε͍ͯΔŋŋŋ͸ͣ

    View Slide

  10. https://speakerdeck.com/qsona/architecture-decision-for-the-next-n-years-at-studysapuri?slide=35

    View Slide

  11. 1SJTNB

    View Slide

  12. 1SJTNB
    • ʮ03.ʯͱ͋Δ͕࣮ࡍ͸ΫΤϦϏϧμʔ ϓϨʔϯͳΦϒδΣΫτΛฦ͢σʔλΞΫηεϥΠϒϥϦ
    • ࠓ೔ͷ࿩ͱ͸͋·Γؔ܎ͳ͍
    ϓϩάϥϛϯάݴޠʹґଘ͠ͳ͍εΩʔϚఆٛɺϚΠάϨʔγϣϯػߏΛ͍࣋ͬͯ
    Δͷ͕௕ॴɻ։ൃऀମݧ͕ͱͯ΋ྑ͍

    View Slide

  13. Domain Model
    GraphQL Mutation
    Repository
    ߋ৽ܥ
    GraphQL Query
    Prisma
    Prisma
    ࣌ʑ
    ബ͍
    %PNBJO૚
    ࢀরܥ
    (SBQI2-1SJTNB$234

    View Slide

  14. Domain Model
    GraphQL Mutation
    Repository
    ߋ৽ܥͷυϝΠϯϞσϧΛͲ͏ॻ͔͘ɻ͔͜͜Β͕ຊ࿦
    ߋ৽ܥ
    GraphQL Query
    Prisma
    Prisma
    薄い
    Domain Layer
    ࢀরܥ

    View Slide

  15. 8FCΞϓϦέʔγϣϯόοΫΤϯυͷ࣮૷ํ๏Λ࠶ߟ͢Δ

    View Slide

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

    View Slide

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

    View Slide

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

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

    イベント
    コマンド

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  23. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  29. model -> model'
    model -> model' model -> model'
    ͓΍

    View Slide

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

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

    View Slide

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

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

    View Slide

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

    View Slide

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

    *0
    PVUQVU

    View Slide

  34. View Slide

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

    View Slide

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

    View Slide

  37. όοΫΤϯυ΋ 5ZQF4DSJQUͰએݴతϓϩάϥϛϯά

    View Slide

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

    View Slide

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

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

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

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

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

  47. ͪ͜ΒΑΓ΋ŋŋŋ
    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

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

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

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

    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

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

    View Slide

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

    View Slide

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

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

    $SFBUFE5BH
    6OWBMJEBUFE5BH

    7BMJEBUFE5BH
    8PSL'MPX

    View Slide

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

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

    View Slide

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

    $SFBUFE5BH
    6OWBMJEBUFE5BH

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

    View Slide

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

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

    *0
    PVUQVU

    View Slide

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

    View Slide

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

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

    View Slide

  62. υϝΠϯϞσϧͷอଘ͸ैདྷ௨Γ 3FQPTJUPSZ
    export const saveCraetedTag =
    ({ prisma }: applicationContext) =>
    (model: CreatedTag): ResultAsync => {
    const { kind: _, ...tag } = model
    const icon = toIconData(tag.icon)
    return ResultAsync.fromPromise(
    prisma.tag.create({
    data: {
    ...tag,
    ...icon,
    },
    }),
    PrismaClientError
    )
    }
    3FTVMU"TZODGSPN1SPNJTFΛ࢖͏͜
    ͱͰ 1SPNJTFΛ 3FTVMUʹแΈɺଞͷ
    3FTVMUܕͱ߹੒Ͱ͖Δ
    ྫ֎΋෧͡ࠐΊΔ͜ͱ͕Ͱ͖Δ

    View Slide

  63. 3FQPTJUPSZύλʔϯʹ͍ͭͯ
    • %PNBJO.PEFM.BEF'VODUJPOBMͰ͸ʮ͜ͷख๏ͳΒ 3FQPTJUPSZ͸ඞཁͳ͍ʯͱ͍͏هड़͕͋Δ
    – ᐌ͘ 3FQPTJUPSZ͸ .VUBCMFͳυϝΠϯϞσϧʹجͮ͘΋ͷ͔ͩΒɺͱͷ͜ͱ
    – ղઆ͕୹͍͜ͱ΋͋Γจҙ͕Α͘௫Ίͳ͔ͬͨŋŋŋ

    • ैདྷ௨Γ 3FQPTJUPSZΛར༻
    – ͨͩ͠3FQPTJUPSZΫϥεͷΦϒδΣΫτͰ͸ͳ͘ɺؔ਺
    – ू໿͸ηΦϦʔ௨Γʹߏங͍ͯ͠Δɻ׶͑ͯݸผͷߋ৽༻ؔ਺ʹ෼ׂ͢Δඞཁ͸ͳ͍ͱߟ͑ͨ

    View Slide

  64. XPSLGMPXʹॾʑॻ͍͍ͯ͘ͱɺτϥϯβΫγϣϯεΫϦϓτʹͳΔ
    • %PNBJO.PEFMJOH.BEF'VODUJPOBM͸τϥϯβΫγϣϯεΫϦϓτ
    • ŋŋŋ͕ɺڽूੑʹ՝୊͕ग़ͦ͏ͳͷͰɺ߲࣍ͷͱ͓ΓϞδϡʔϧԽͨ͠

    View Slide

  65. ΤϯςΟςΟͷܕΛج఺ʹͨ͠ϞδϡʔϧΛ࡞ΓɺίΞυϝΠϯϩδοΫؔ਺͸ͦ͜ʹूΊΔ
    // customers/objects/tag.ts
    export interface Tag {
    id: TagId
    groupId: RestaurantGroupId
    label: TagLabel
    icon: TagIcon
    sortOrder: number
    builtin: boolean
    }
    export const updateLabel = (label: TagLabel) => (tag: Tag) => ({ ...tag, label })
    export const updateIcon = (icon: TagIcon) => (tag: Tag) => ({ ...tag, icon })

    View Slide

  66. τϥϯβΫγϣϯεΫϦϓτͰ͸ͳ͘ͳΔ
    • ίΞυϝΠϯΦϒδΣΫτͷपΓʹɺίΞυϝΠϯϩδοΫؔ਺͕ू·Δ
    • XPSLGMPX͸ͦͷίΞυϝΠϯϩδοΫؔ਺Λݺͼग़ͯ͠ɺۀ຿ϑϩʔΛ૊ΈཱͯΔ໾ׂʹ
    – ΦχΦϯΞʔΩςΫνϟͳͲͷ 6TF$BTFͱಉ͡໾ׂ
    ΞϓϦέʔγϣϯΞʔΩςΫνϟͷશ
    ମ૾͸ɺैདྷͷΞʔΩςΫνϟͱͦΕ
    ΄Ͳେ͖͘มΘ͍ͬͯͳ͍

    View Slide

  67. έʔεͦͷطଘͷΤϯςΟςΟͷߋ৽
    • ͖͞΄Ͳ·Ͱͷ͸ΤϯςΟςΟͷ৽ن࡞੒
    • طଘͷΤϯςΟςΟΛߋ৽͢ΔΑ͏ͳϢʔεέʔεͷ৔߹ŋŋŋ
    – σʔλϕʔε͔ΒΤϯςΟςΟΛ෮ݩ͢Δ
    – ͦͷΤϯςΟςΟͱ͸ผʹɺೖྗ͕͋Δ

    View Slide

  68. 7BMJEBUFE
    *OQVU
    6OWBMJEBUFE
    *OQVU
    6QEBUFE
    5BH
    5BH
    (SBQI2-
    %#
    3FQPTJUPSZ

    ͳΜ͔ͩͪ͝Όͭ͘ŋŋŋ

    View Slide

  69. (SBQI2-
    %#
    3FQPTJUPSZ

    *OQVU
    5BH
    6OWBMJEBUFE
    $PNNBOE
    7BMJEBUFE
    $PNNBOE
    ŋŋŋ 6QEBUFE5BH
    8PSL'MPX
    HFU5BH#Z*E

    ೖྗͱυϝΠϯΦϒδΣΫτΛͻͱͭʹ·ͱΊͨʮίϚϯυʯΛϫʔΫϑϩʔͷೖྗʹ͢Δ
    ͦͷ্Ͱɺઌ΄Ͳಉ༷ɺ୯ํ޲σʔλ
    ϑϩʔͷதͰঢ়ଶભҠͤͯ͞໨తͷग़
    ྗʹ͚͍ۙͮͯ͘

    View Slide

  70. // customers/workflows/updateTag.ts
    interface UnvalidatedInput {
    kind: 'Unvalidated'
    label: string
    icon?: { symbol: string; type: TagIconType; color?: string | null | undefined } | null | undefined
    }
    interface UnvalidatedCommand {
    input: UnvalidatedInput
    tag: Tag
    }
    interface ValidatedInput {
    kind: 'Validated'
    label: TagLabel
    icon: TagIcon
    }
    interface ValidatedCommand {
    input: ValidatedInput
    tag: Tag
    }
    // Output
    type UpdatedTag = Tag & { kind: 'Updated' }

    View Slide

  71. // customers/workflows/updateTag.ts
    // substep1: validateCommand
    type validateCommand = (command: UnvalidatedCommand) => Result
    // substep2: updateTag
    type updateTag = (command: ValidatedCommand) => Result
    // workflow: validateCommand -> updateTag
    type WorkFlow = (command: UnvalidatedCommand) => Result
    export const updateTagWorkFlow = (): WorkFlow => (command) =>
    ok(command).andThen(validateCommand).andThen(updateTag)

    View Slide

  72. (SBQI2-
    %#
    3FQPTJUPSZ

    *OQVU
    5BH
    6OWBMJEBUFE
    $PNNBOE
    7BMJEBUFE
    $PNNBOE
    ŋŋŋ 6QEBUFE5BH
    8PSL'MPX
    HFU5BH#Z*E

    View Slide

  73. // customers/repos/tagRepository.ts
    export const findTagById =
    ({ prisma }: applicationContext) =>
    (id: TagId): ResultAsync =>
    ResultAsync.fromPromise(
    prisma.tag.findUnique({ where: { id } }),
    PrismaClientError
    ).andThen((tag) => (tag ? Tag(tag) : ok(null)))
    export const getTagById =
    (context: applicationContext) =>
    (id: TagId): ResultAsync =>
    findTagById(context)(id).andThen((tag) =>
    tag ? ok(tag) : err(new EntityNotFound(`タグがみつかりません: ${id}`))
    )

    View Slide

  74. // graphql/mutation/updateTag.ts
    export const updateTagMutation = mutationField('updateTag', {
    type: UpdateTagPayload,
    args: {
    tagId: idArg({ description: 'タグID' }),
    input: TagInput,
    },
    resolve(_root, { tagId, input }, context) {
    const workflow = updateTagWorkFlow()
    const preprocess = TagId(tagId)
    .asyncAndThen(getTagById(context))
    .map((tag) => toUnvalidatedCommand({ input, tag }))
    const result = preprocess.andThen(workflow).andThen(saveTag(context))
    return result.match( ... )
    },
    })

    View Slide

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

    *0
    PVUQVU

    View Slide

  76. ΑΓෳࡶͳϑϩʔ͸
    • جຊతʹ͸ߏ଄͸ಉ͡Ͱɺߏ଄ࣗମ͸ෳࡶʹͳΒͳ͍
    • ঢ়ଶભҠͷαϒεςοϓ͕૿͍͑ͯ͘

    View Slide

  77. έʔεͦͷυϝΠϯϩδοΫͷ్தͰ *0͕ൃੜ͢Δ৔߹
    • ඞͣ͠΋υϝΠϯϩδοΫ࣮ߦલʹಡΈࠐΈΛࡁ·ͤΒΕΔέʔε͹͔ΓͰ͸ͳ͍
    • ͨͱ͑͹ŋŋŋ
    – ࡞੒ͨ͠ΤϯςΟςΟͷฒͼҐஔΛɺσʔλϕʔεʹ໰͍߹Θܾͤͯఆ͢Δ
    – υϝΠϯϞσϧͷߋ৽ʹɺԿΒ͔֎෦ͷ 8FC "1*Λίʔϧͯ͠औಘͨ͠஋Λར༻͢Δ
    – ŋŋŋ

    View Slide

  78. %FQFOEFODZ*OKFDUJPOΛ࢖͏
    • *0ͦͷ΋ͷΛۀ຿ॲཧͷ్த͔ΒऔΓআ͚ͳ͍ͳΒɺ%*Ͱ XPSLGMPX͕ *0 ʹ·ͭΘΔܕ ྫ σʔλ
    ϕʔε઀ଓΦϒδΣΫτ
    ʹґଘ͠ͳ͍Α͏ʹ͢Δ
    – XPSLGMPX͸ *0ʹґଘ͠ͳ͍ؔ਺Ͱ͋Δ͜ͱΛҡ࣋͢Δ
    • %*͸ΧϦʔԽʹΑΔ෦෼ద༻Ͱ࣮ݱ͢Δ
    – %PNBJO.PEFM .BEF'VODUJPOBMͰఏҊ͞Ε͍ͯͨख๏

    View Slide

  79. // customer/services/tag.ts
    export type getTagSortOrder = ({ groupId }: { groupId: RestaurantGroupId })
    => ResultAsync
    export const getTagSortOrder =
    ({ prisma }: applicationContext): getTagSortOrder =>
    ({ groupId }) =>
    ResultAsync.fromPromise(
    prisma.tag
    .aggregate({
    _max: {
    sortOrder: true,
    },
    where: { groupId },
    })
    .then((x) => (x._max.sortOrder ? x._max.sortOrder + 1 : 1)),
    PrismaClientError
    )
    ΧϦʔԽʹΑΓɺ෦෼ద༻Ͱ͖ΔΑ͏
    ʹ͢Δ

    View Slide

  80. // graphql/mutation/createTag.ts
    export const createTagMutation = mutationField('createTag', {
    type: CreateTagPayload,
    args: {
    input: TagInput,
    },
    resolve(_root, { input }, context) {
    const workflow = createTagWorkFlow(checkTagExists(context), getTagSortOrder(context)) // DI
    const unvalidatedTag = toUnvalidatedTag({
    ...input,
    groupId: context.operator.groupId,
    })
    const result = workflow(unvalidatedTag).andThen(saveCreatedTag(context))
    return result.match(...)
    },
    })
    ϫʔΫϑϩʔʹ͸ίϯςΩετ
    ͕෦෼ద༻͞Εͨؔ਺Λ౉͢

    View Slide

  81. // customer/workflows/createTag.ts
    type createTag = (model: ValidatedTag) => ResultAsync
    const createTag =
    (getTagSortOrder: getTagSortOrder): createTag =>
    (model) => {
    const values = getTagSortOrder({ groupId: model.groupId })
    return values.map((n) => ({
    ...model,
    kind: 'Created',
    id: generateTagId(),
    sortOrder: n,
    builtin: false,
    }))
    }
    // workflow: validateTag -> createTag
    type WorkFlow = (model: UnvalidatedTag) => ResultAsync
    export const createTagWorkFlow =
    (
    checkTagExists: checkTagExists, // dependency
    getTagSortOrder: getTagSortOrder // dependency
    ): WorkFlow =>
    (model) =>
    okAsync(model).andThen(validateTag(checkTagExists)).andThen(createTag(getTagSortOrder))
    ϫʔΫϑϩʔ͔Β͸σʔλϕʔείϯ
    ςΩετ͕ཁΒͳ͍७ਮؔ਺ʹݟ͑Δɻ
    ͭ·Γɺςετ΍σόοά࣌ɺ७ਮؔ
    ਺ʹࠩ͠׵͑Δ͜ͱ΋Ͱ͖Δ
    %*͞Εͨؔ਺Λ஫ೖͭͭ͠΋ɺσʔλ
    ϑϩʔ͸͜Ε·Ͱ௨Γͷߏ଄
    HFU5BH4PSU0SEFS΋ 3FTVMUΛฦ͢
    ͷͰɺσʔλϑϩʔͷதͰ݁Ռ͸߹੒
    ͞ΕΔ

    View Slide

  82. %*Ͱղܾ͢ΔΑ͏ͳ *0Ͱ͸ͳ͍৔߹͸ϫʔΫϑϩʔͷαϯυΠονʹ͢Δͱྑ͍ɺͱͷ͜ͱ
    https://www.slideshare.net/ScottWlaschin/reinventing-the-transaction-script-ndc-london-2020

    View Slide

  83. ߟ࡯

    View Slide

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

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

    View Slide

  85. ैདྷͷΞʔΩςΫνϟͱͷࠩҟʹ͍ͭͯ
    • ్தͰ৮Εͨͱ͓Γɺେ࿮ͷΞʔΩςΫνϟ͸͋·ΓมΘ͍ͬͯͳ͍
    – 6TF$BTF૬౰ͷ XPSLGMPX
    – 3FQPTJUPSZ
    – ίΞυϝΠϯϞσϧ
    – ू໿ɺΤϯςΟςΟ
    • ίϯϙʔωϯτͷதͷ࣮૷ͷύϥμΠϜ͕ҟͳΔ
    – ܕͰۀ຿ͷঢ়ଶɺϑϩʔΛએݴ͢Δ
    – σʔλϑϩʔϓϩάϥϛϯάʹΑΔɺ୯ํߴσʔλϑϩʔ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  90. ัଊ
    • 5ZQF4DSJQUʹ૊ΈࠐΈͷ 3FTVMUܕ͸ͳ͍ͷͰɺOFWFSUISPXΛ࢖ͬͨɻଞʹ΋ GQUTͳͲͷީิ͕͋Δ
    • 3FTVMUܕ͸ XPSLGMPXͷߏ੒͚ͩͰͳ͋͘ΒΏΔ৔ॴͰ࢖͏
    • 1SPNJTF΋ 3FTVMU"TZODʹΑͬͯ 3FTVMUԽͯ͠߹੒Ͱ͖Δ
    • UZQFͱ JOUFSGBDFͷ࢖͍෼͚ ŋŋŋ GQUTʹ฿͍ͬͯΔɻಛʹͦ͏͠ͳ͚Ε͹ͳΒ͍ཧ༝͸ͳ͍ͱࢥ͏
    • ؔ਺͕σϑΥϧτͰΧϦʔԽ͞Εͳ͍ͷ͸࢓ํ͕ͳ͍ɻ࣌ંΧϦʔԽͨ͠ͷΛ๨Εͯ͸·Δ
    • SFBEPOMZ͸ԣணͯ͠ɺ࢖ͬͯͳ͍ɻ ͪΌΜͱݕ౼͍ͯ͠ͳ͍

    View Slide

  91. ͓·͚ ŋŋŋ OFXUZQF׬શίϯετϥΫλ
    declare const __newtype: unique symbol
    export type newtype = Type & {
    readonly [__newtype]: Constructor
    }
    export type TagId = newtype<'TagId', string>
    export function TagId(value: string): Result {
    return validate(value)
    ? ok(value as TagId)
    : err(new ValidationError('IDの形式が不正です'))
    }
    υϝΠϯϞσϧͷߏ੒ʹ
    /PNJOBMͳܕ͕ཉ͍͠ͱ͖͸ɺ͜͏͍
    ͏࣮૷Ͱ΍ͬͯ·͢

    View Slide

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

    View Slide