Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
フロントエンドのパラダイムを参考にバックエンド開発を再考する / TypeScript による...
Search
Naoya Ito
October 01, 2022
Technology
25k
67
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
フロントエンドのパラダイムを参考にバックエンド開発を再考する / TypeScript による GraphQL バックエンド開発
2022年10月1日に開催された #postdev での発表です
Naoya Ito
October 01, 2022
More Decks by Naoya Ito
See All by Naoya Ito
アルゴリズムは何を圧縮しているのか ─ Haskell から育った「圧縮代数」というメンタルモデル
naoya
16
4.6k
Haskell を武器にして挑む競技プログラミング ─ 操作的思考から意味モデル思考へ
naoya
13
4.9k
Haskell でアルゴリズムを抽象化する / 関数型言語で競技プログラミング
naoya
21
8.1k
Functional TypeScript
naoya
19
6.9k
TypeScript 関数型スタイルでバックエンド開発のリアル
naoya
77
38k
シェルの履歴とイクンリメンタル検索を使う
naoya
16
6.9k
20230227-engineer-type-talk.pdf
naoya
91
88k
関数型プログラミングと型システムのメンタルモデル
naoya
64
110k
TypeScript による GraphQL バックエンド開発
naoya
29
38k
Other Decks in Technology
See All in Technology
少ない人手 限られた予算 それでも守る 消せない・見つける・戻れる オールハザード型BCP on AWS / 20261107 Mitsutoshi Matsuo
shift_evolve
PRO
0
310
Ebitengine製iOSアプリ公開ボタンを押す会
eiei114
0
110
[General] 개발자로 해외 기업에서 일하기
skydoves
1
310
81天的 AI Agent 軟體開發團隊治理實務分享
regionbbs
0
130
Kiro Crew 入門 (手を離しても進む仕事) | 目指せ Kiro マスター 11
inariku
PRO
4
650
新卒エンジニア、 介護士になる
ryu1013
1
690
自治体AIコンタクトセンター、何をどこに任せる?~Amazon Connect Customer +Amazon Bedrockの実践知~ / 20261107 Satoshi Torano
shift_evolve
PRO
1
120
Building Audio Plugin Ecosystem for Android in 2026
atsushieno
2
160
GCASアップデート(202607-202610)
techniczna
1
240
超⼩型・多⾔語検索モデル Bekko Embedding
hotchpotch
1
140
re:Invent2026 事前勉強会 歴史と愉しみ方10分LT編
toshi_atsumi
0
170
AWS構成から考えるWebセキュリティ ― WordPressの構築経験から考える公開範囲と更新運用【JAWS FESTA 2026 in AKITA】
cscengineer
PRO
2
380
Featured
See All Featured
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
508
140k
Agile that works and the tools we love
rasmusluckow
331
22k
Facilitating Awesome Meetings
lara
57
7.2k
Darren the Foodie - Storyboard
khoart
PRO
4
4.1k
The agentic SEO stack - context over prompts
schlessera
0
1k
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
3
1.4k
First, design no harm
axbom
PRO
2
1.4k
For a Future-Friendly Web
brad_frost
184
11k
30 Presentation Tips
portentint
PRO
1
460
Product Roadmaps are Hard
iamctodd
55
13k
Ecommerce SEO: The Keys for Success Now & Beyond - #SERPConf2024
aleyda
1
2.2k
A Guide to Academic Writing Using Generative AI - A Workshop
ks91
PRO
1
560
Transcript
ϑϩϯτΤϯυͷύϥμΠϜΛࢀߟʹόοΫΤϯυ։ൃΛ࠶ߟ͢Δ 5ZQF4DSJQUʹΑΔ (SBQI2-όοΫΤϯυ։ൃ גࣜձࣾ Ұٳ ҏ౻
K2VFSZظ .1"XJUIK2VFSZ ʮ+BWB4DSJQU͕ಘҙʯͳਓ͍͕ͨɺϑϩϯτΤϯυ όοΫΤϯυͱ͍͏ׂߦΘ Εͣɺશһ͕ಉ͡ྖҬΛ୲͍ͯͨ͠
7VFKTಋೖظ .1" 7VFϑϩϯτΤϯυ ʮϑϩϯτΤϯυΤϯδχΞʯʮόοΫΤϯυΤϯδχΞʯͱ͍͏ׂ͕গͣͭ͠ͳ͞Ε ΔΑ͏ʹ
/VYU࣌ /VYU (SBQI2-όοΫΤϯυ ʮϑϩϯτΤϯυʯʮόοΫΤϯυʯͷׂ͕໌֬ʹ ϑϩϯτΤϯυͷΈ։ൃΛ͢Δɺͱ͍͏୲ऀ
ํͷٕज़తؔ৺ࣄʹΪϟοϓ • ΞϓϦέʔγϣϯͷঢ়ଶཧϞσϧ • σβΠϯγεςϜ • ϓϦϨϯμϦϯά • ŋŋŋ ϑϩϯτΤϯυ
όοΫΤϯυ • υϝΠϯϞσϧ • ϨΠϠʔυɾΞʔΩςΫνϟ • $234 • ŋŋŋ ৫ͷٕज़࿅্͕͕Ε্͕Δ΄Ͳɺؔ৺ࣄͷΪϟοϓ͕͕͍ͬͯͬͨ
͖͔͚ͬ • ৽نϓϩδΣΫτ্ཱ͕͕ͪΔ • ϑϩϯτΤϯυ 3FBDU3FMBZ3FDPJM Λ࠾༻ • (SBQI2-όοΫΤϯυ ŋŋŋ
Β͘ 1ZUIPOͰɺΫϥεΛଟ༻ͨ͠ΫϦʔϯΞʔΩςΫνϟ తͳઃܭͰ͖͕ͬͯͨɺࠓճͲ͏͢Δ͔
৽نϓϩμΫτখ͞ͳνʔϜͰີߴٞͯ͘͠࡞Γ͍ͨ • ސ٬ͷͲΜͳΛɺͲ͏ղܾ͍͔ͨ͠ ŋŋŋ σΟεΧογϣϯΛେࣄʹ͍ͨ͠ – ϑϩϯΤϯυɺόοΫΤϯυؔͳ͘ରυϝΠϯྖҬʹৄ͘͠ͳΓ͍ͨ – ͲΜͳମݧΛɺͲΜͳ 6*ͰɺͲ͏͍͏ϞσϧͰɺͲΜͳσʔλઃܭΛ࣮ͯ͠ݱ͢Δͷ͔ɻॳظ
ϑΣʔζͰશһ͕ͳΔ͓͖͍ͬͯͨ͘͘ • ૣظʹׂΛߦ͍͗͢Δͱɺؔ৺ࣄͷஅΛ༠ൃͯ͠͠·͏
3FBDUͰϑϩϯτΤϯυΛ։ൃ͔ͯ͠Βɺ όοΫΤϯυΛॻ͘ͱŋŋŋ • 3FBDUŋŋŋ খ͞ͳؔΛΈ߹Θͤͯએݴతʹॻ͍͍ͯ͘ • όοΫΤϯυ ŋŋŋ ΫϥεΛͨ͘͞Μॻ͍ͯɺϨΠϠʔΛލ͙ͱ %50Ͱͷ٧Ίସ͑Λߦͬ
ͯɺJOUFSGBDFͰґଘੑͷٯసΛߦͬͯŋŋŋ – ʮŋŋŋϑϩϯτΤϯυͩͱ͜͏͍͏͜ͱɺ͋Μ·ΓΒͳ͍ΑͶʯ ։ൃ࣌ͷϝϯλϧϞσϧͷΪϟοϓ͕େ͖͍ ίϯςΩετεΠονͷෛ୲େ͖͍
όοΫΤϯυ։ൃͷΓํΛ࠶ߟͯ͠Έ͍ͨ • 3FBDUΛ͍ͬͯΔͱϑϩϯτΤϯυബ͘ॻ͘͜ͱ͕Ͱ͖Δ • ؔ৺ࣄ͕ҧ͏ͷવɻ͔ͱ͍ͬͯɺΓํ͕ҧ͏ͷΛશٙ͘Θͳ͍ͷͲ͏ͩΖ͏ ϑϩϯτΤϯυͷঢ়ଶཧෳࡶ ͦͷෳࡶͳͷΛͲ͏ѻ͏͔ɺݱ࣌Ͱ࠷ྑͷϞσϧͷͻͱ͕ͭ 3FBDUͷͣ ʮෳࡶͳঢ়ଶΛͲ͏ѻ͏͔ʯͱ͍͏؍ͰɺαʔόʔαΠυಉ͡Α͏ʹߟ͑ΒΕͳ͍ͷ͔ Α͠ɺ(SBQI2-όοΫΤϯ
υ 5ZQF4DSJQUͰॻ͍ͯΈ Α͏
վΊͯɺࡢࠓͷϑϩϯτΤϯυͷϓϩάϥϛϯάύϥμΠϜΛߟ͑ͯΈΔ https://zenn.dev/mizchi/articles/oop-think-modern
&MNΞʔΩςΫνϟ https://guide.elm-lang.jp/architecture/
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ؔ ΛݺͿɻؔʹΠϕϯτͷछ ྨʹԠͨ͡Ϟσϧͷঢ়ଶભҠΛ هड़͓ͯ͘͠
ঢ়ଶભҠͷؔ ֎քͱΓͱ Γ *0 イベント コマンド
model -> model' model -> model' model -> model' ΠϕϯτΛܖػʹঢ়ଶ͕ભҠ͢Δ
ŋŋŋ ࣌ܥྻʹج͍ͮͨঢ়ଶ
ঢ়ଶભҠͷؔ ϥϯλΠϜϑ ϨʔϜϫʔΫ イベント コマンド Πϕϯτʹ͍ঢ়ଶΛભҠͤͯ͞ɺ͋ͱϑϨʔϜϫʔΫϥϯλΠϜʹͤΔ
3FEVY"QQMJDBUJPO%BUB'MPX https://redux.js.org/tutorials/essentials/part-1-overview-concepts
3FDPJM function TextInput() { const [text, setText] = useRecoilState(textState); const
onChange = (event) => { setText(event.target.value); }; return ( <div> <input type="text" value={text} onChange={onChange} /> <br /> Echo: {text} </div> ); }
3FDPJM • 3FEVYʹΑΔେ͖ͳάϩʔόϧεςʔτɺѻ͍ͮΒ͍ہ໘͋ͬͨ • ΑΓείʔϓΛڱ͘ɺখ͘͞ɺϩʔΧϧεςʔτ VTF4UBUF ಉ༷ʹϑοΫͰએݴతʹѻ͍ ͍ͨ &MNΞʔΩςΫνϟ 3FEVYͰൃݟ͞Εͨྑ͍ϓϥΫςΟε౿ऻͭͭ͠ɺͰ͖Δݶ
Γখ͞ͳείʔϓͰঢ়ଶΛѻ͍͚ͬͯΔͱྑͦ͞͏
None
όοΫΤϯυͰಉ͡Α͏ʹʮ࣌ܥྻʹجͮ͘ঢ়ଶભҠʯͷࢹͰߟ͑ΒΕͳ͍͔ • όοΫΤϯυͷੈքͷओͳʮঢ়ଶʯ ŋŋŋ υϝΠϯϞσϧͷঢ়ଶ • υϝΠϯϞσϧͷঢ়ଶΛભҠͤ͞ΔΠϕϯτ ŋŋŋ υϝΠϯΠϕϯτ
ͨͱ͑ʮ॓ധ༧ʯΛྫʹυϝΠϯϞσϧΛվΊͯߟ͑ͯΈΔ • ͲΜͳ؍ʹͯ͠ߟ͑ͯΈΔ͖͔ – σʔλߏ – &3ਤ – Ϋϥεͷ࣮ –
ը໘ • ͍ͣΕ੩తͳߏʹযΛ͍ͯͯΔɻࢹΛม͑ͯΈ͍ͨ – ಈతͳͷŋŋŋυϝΠϯΠϕϯτঢ়ଶʹযΛͯͯΈΔͱ
ʮ༧ʯͷঢ়ଶભҠʹணͯ͠ΈΔ ༧ྃ Χʔυܾࡁ ࡁΈ Ωϟϯηϧ ॓ധࡁΈ
৽ن༧͕ྃ͢Δલ͔ΒυϝΠϯϞσϧଘࡏ͍ͯ͠Δ ༧ྃ Ωϟϯηϧ ॓ധࡁΈ ೖྗ ݕূࡁΈ ೖྗະݕূ ࡏݿ֬อ ࡁΈ
ঢ়ଶԿ͔͠ΒͷΠϕϯτΛܖػʹભҠ͢Δ ༧ྃ Ωϟϯηϧ ॓ധࡁΈ ೖྗ ݕূࡁΈ ೖྗະݕূ ࡏݿ֬อ ࡁΈ ༧Λ։࢝ͨ͠
ݕূ͕ྃͨ͠ ࡏݿΛ֬อͨ͠ ༧Λߦͬͨ Ωϟϯηϧ͞Εͨ ॓ധͨ͠
model -> model' model -> model' model -> model' ͓
྆֎෦ͱͷΠϯλϑΣʔε model -> model model -> model &WFOU)BOEMFS 8FC"QQͳΒ SPVUFS
%#ʹอଘ͠ Ϩεϙϯε 6*
model -> model model -> model event &WFOU)BOEMFS 8FC"QQͳΒ SPVUFS
%#ʹอଘ͠ Ϩεϙϯε 6* event event ֎ͷੈք ֎ͷੈք ֎ͷੈք Πϕϯτ ˠϞσϧͷঢ়ଶભҠ 🤔 Ͳ͔͜Ͱݟͨͳŋŋŋ
ঢ়ଶભҠͷؔ ϥϯλΠϜϑ ϨʔϜϫʔΫ イベント コマンド ಉ͡
*0ঢ়ଶભҠ *0 Pure function Model -> Model *0 JOQVUMPBE *0
PVUQVU
None
https://www.slideshare.net/ScottWlaschin/reinventing-the-transaction-script-ndc-london-2020
ϑϩϯτΤϯυͱόοΫΤϯυͷঢ়ଶཧ • ϑϩϯτΤϯυͷঢ়ଶཧ ŋŋŋ ओͳؔ৺ࣄʮΞϓϦέʔγϣϯͷঢ়ଶʯ • όοΫΤϯυͷঢ়ଶཧ ŋŋŋ ओͳؔ৺ࣄʮυϝΠϯϞσϧɺυϝΠϯΦϒδΣΫτͷঢ় ଶʯ
ཧ͍ͯ͠Δঢ়ଶͷίϯςΩετҧ͏ͷͷ ঢ়ଶཧͷϞσϧࣅͨΑ͏ʹߟ͑ΒΕΔͷͰͳ͍͔
ʮυϝΠϯΦϒδΣΫτͷঢ়ଶભҠΛએݴతʹهड़ͭͭ͠ *0͔Β͢Δʯ • ͜ͷίϯηϓτͰ࣮ • &MNΞʔΩςΫνϟ͓Αͼ '%%%ຊΛࢀߟʹ
͜ͷελΠϧͰΑ͘͏ͷ • type / interface • λά͖ϢχΦϯ ܕ • 3FTVMUܕ
• ΧϦʔԽ • ܕͷϒϥϯυԽ ίϯύχΦϯΦϒδΣΫτ
͋·ΓΘͳ͍ͷ • class • ྫ֎ͷ throw – Error Ϋϥε͍·͢
؆୯ͳϢʔεέʔεྫ
5BHΤϯςΟςΟ ूϧʔτ ͷঢ়ଶભҠʹண͢Δ 7BMJEBUFE 6OWBMJEBUFE $SFBUFE ೖྗ͕͋ͬͨ ݕূͨ͠ ࡞ͨ͠ ˞͜͜Ͱͷʮ$SFBUFEʯ͋͘·ͰυϝΠϯΦϒδΣΫτ͕
l࡞ࡁΈzʹͳͬͨঢ়ଶͰ͋ͬͯɺσʔλϕʔεʹϨίʔυ ΛՃͨ͠ɺͱ͍͏ঢ়ଶͰͳ͍
5BHΫϥεΛ࡞Δ export class Tag { state: 'Unvalidated' | 'Validated' |
'Created', id: TagId | undefined, groupId: RestaurantGroupId, label: string, icon: TagIcon | undefined, sortOrder: number | undefined, builtin: boolean | undefined }
export class Tag { state: 'Unvalidated' | 'Validated' | 'Created',
id: TagId | undefined, groupId: RestaurantGroupId, label: string, icon: TagIcon | undefined, sortOrder: number | undefined, builtin: boolean | undefined } ঢ়ଶભҠલʹ֬ఆ͠ͳ͍ϓϩύςΟ͕ VOEFGJOFE ʹͳͬͯ͠·͏ŋŋŋ
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 ͦ͜Ͱঢ়ଶ͝ͱʹܕΛఆٛ͢Δ ঢ়ଶ͕ભҠ͢Δ υϝΠϯΠϕ ϯτ͕ൃੜ͢Δ͝ͱʹϞσϧͷ ͕֬ఆ͍ͯ͘͠ͷ͕એݴͰ͖͍ͯ Δ
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 ʯ
ͪ͜ΒΑΓŋŋŋ export class Tag { state: 'Unvalidated' | 'Validated' |
'Created', id: TagId | undefined, groupId: RestaurantGroupId, label: string, icon: TagIcon | undefined, sortOrder: number | undefined, builtin: boolean | undefined }
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 ͪ͜Βͷํ͕ɺͷΈ߹Θͤύλʔϯ͕গͳ͘ݫີ
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(), } } ঢ়ଶΛભҠͤ͞Δεςοϓ ؔ
ঢ়ଶΛભҠͤ͞Δεςοϓ ؔ type createTag = (model: ValidatedTag) => CreatedTag
const createTag: CreatedTag = (model) => { return { ...model, kind: 'Created', id: generateTagId(), sortOrder: getTagSortOrder({ groupId: model.groupId }), builtin: false, } } ४උ͕ͬͯॳΊ͕ͯ֬ఆ ͢ΔͷΛࣗવʹهड़Ͱ͖Δ ͳ͓ getTagSortOrder *0͕͋Δ ͨΊ %*͍ͯ͠ΔɻࠓճׂѪ
ϞσϧͷܕɺؔͷܕʹΑͬͯঢ়ଶભҠΛએݴతʹهड़͢Δ 7BMJEBUFE 6OWBMJEBUFE $SFBUFE (model: UnvalidatedTag) => ValidatedTag (model: ValidatedTag)
=> CreatedTag
ݸผʹఆٛͨ͠ঢ়ଶભҠͷؔΛܨ͍͛ͨ • Ͱɺܭࢉʮ్தͰࣦഊ͢ΔʯՄೳੑ͕͋Δ – ͨͱ͑υϝΠϯϞσϧͷࣄલ݅Λຬͨ͞ͳ͍Τϥʔ – 7BMJEBUJPO&SSPSŋŋŋ – .BY5BH-JNJU&YDFFEFEʜ •
ʮ్தͰࣦഊ͢Δʯ͜ͱΛܕͰએݴͰ͖ͳ͍͔ ˠ 3FTVMUܕ
3FTVMUܕͰࣦഊͷՄೳੑͷ͋ΔܭࢉΛҰຊಓʹ߹͢Δ import { Result, ok, err } from 'neverthrow' function
itsUnder100(n: number): Result<number, Error> { return n <= 100 ? ok(n) : err(new Error('100より大きい数字です')) } function itsEven(n: number): Result<number, Error> { return n % 2 == 0 ? ok(n) : err(new Error('奇数です')) } function itsPositive(n: number): Result<number, Error> { 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 } )
3FTVMUܕͰঢ়ଶભҠؔΛͭͳ͛ͯɺҰͭͷʮϫʔΫϑϩʔʯΛ࡞Δ 7BMJEBUFE5BH $SFBUFE5BH 6OWBMJEBUFE5BH 7BMJEBUFE5BH 8PSL'MPX
type validateTag = (model: UnvalidatedTag) => Result<ValidatedTag, ValidationError> 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ܕΛฦ͢
3FTVMUܕͰঢ়ଶભҠؔΛܨ͛ͯɺϫʔΫϑϩʔ υϝΠϯϩδοΫ Λ࡞Δ type WorkFlow = (model: UnvalidatedTag) => Result<CreatedTag,
CreateTagError> export const createTagWorkFlow: WorkFlow = (model) => ok(model).andThen(validateTag).andThen(createTag)
ϫʔΫϑϩʔͷ࢝·ΓͱऴΘΓ͕ɺ֎քͱͷ JOPVU 7BMJEBUFE5BH $SFBUFE5BH 6OWBMJEBUFE5BH 7BMJEBUFE5BH 8PSL'MPX ೖྗͷ%50
ྫ(SBQI2-*OQVU5ZQF Λ 6OWBMJEBUFE5BHʹม UBH3FQPTJUPSZͰ $SFBUFE5BHΛอଘ
(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 } ) }, })
ok(model).andThen(workflow).andThen(saveCreatedTag(context)) (SBQI2-*OQVU σʔλϕʔε Pure function Model -> Model *0 JOQVUMPBE
*0 PVUQVU
ঢ়ଶભҠͷؔ ϥϯλΠϜϑ ϨʔϜϫʔΫ イベント コマンド
σʔλϑϩʔϓϩάϥϛϯά • 3FTVMUܕͰࣦഊͷ͋ΔܭࢉΛ߹͠ɺσʔλͷ௨Γಓͱͯ͠ͷܭࢉաఔΛ࡞Δ – ͦ͜ʹσʔλΛ์ΓࠐΉͱɺͦͷதΛ௨ͬͯঢ়ଶભҠͨ͠σʔλ͕ಘΒΕΔ – σʔλΛσʔλͷ··ɺͦͷՄൖੑΛԼ͛ͣʹѻ͍͍ͨɻ݁Ռ class ͷొػձ͕ͳ͍ •
ܭࢉΛҰຊಓʹ͢Δ – େҬग़͠ͳ͍ɻେҬग़͢Δͱܭࢉ͕ҰຊಓʹͳΒͳ͍ ˠྫ֎ΛΘͳ͍ – ࣦഊͷذ 3FTVMUͰ߹ ˞3FTVMUܕϞφυ – ܭࢉ͕ҰຊಓʹͳΔ σʔλෆมɻೝෛՙ͕͘ͳΔ
ϑϩϯτΤϯυͱͷൺֱ • ࣌ܥྻʹجͮ͘ঢ়ଶભҠ σʔλϑϩʔ Λએݴతʹهड़͢Δɺͱ͍͏ߟ͑ํಉ͡ʹͳͬͨ – ܕͱখ͞ͳؔͷએݴతͳهड़ͰɺϑϩʔΛΈ্͛Δ • ҰํɺϫʔΫϑϩʔͷ࣮Λ͍ͯ͠Δͱ͖ͷײ֮ʹ·ͩڑ͕͋Δ –
υϝΠϯΠϕϯτͰঢ়ଶભҠɺͱ͍ͬͯଟ͘ͷ߹ʮ7BMJEBUFͯ͠ɺೖྗͰυϝΠϯϞσϧ Λߋ৽͢Δʯ͚ͩ • ݁ՌɺϫʔΫϑϩʔఆܕతͳهड़͕ଟ͘ͳΔ ŋŋŋ ϑϨʔϜϫʔΫԽͰ͖Δ͔ • ϑϩϯτΤϯυͦ͜Λ 3FBDU &MNͳͲͷϑϨʔϜϫʔΫ͕͍ͬͯΔ ͔ͩΒɺΠϕϯτʹର͢ΔϞ σϧͷঢ়ଶભҠͱɺͦͷঢ়ଶΛදݱ͢ΔϓϨθϯςʔγϣϯͷهड़ʹूதͰ͖Δ • ΠϕϯτͱΠϕϯτͷͭͳ͗߹Θͤ ྫ3FTVMUܕʹΑΔ߹ ΛࣗͰهड़͍ͯ͠Δͷ͕ݱঢ়
͕࣌ؒແ͍ͷͰɺ࣮ͷৄࡉ·ͨޙ • ͜ͷ··ͰτϥϯβΫγϣϯεΫϦϓτͰɺڽूੑ͕͘ͳͬͯ͠·͏ • ΤϯςΟςΟܕͷपลʹɺίΞυϝΠϯϩδοΫͷؔΛ࣮ͯ͠Ϟδϡʔϧׂ͢Δ • ϫʔΫϑϩʔͦͷίΞυϝΠϯϩδοΫΛͬͯϑϩʔΛΈཱͯΔׂ͚ͩʹͳΔ – ΫϦʔϯɾΞʔΩςΫνϟͷ 6TF$BTFͱಉ͡
ैདྷख๏ʹൺֱ͠هड़ྔগͳ͍ • σʔλΛσʔλͷ··ӡΜͰ͍ΔͷͰʮ٧Ίସׂ͑ͯͷҟͳΔผछͷΦϒδΣΫτʯʹ͢ Δŋŋŋͱ͍͏ඞཁ͕ͳ͍ – ͷίϐʔ࣮ࡍʹͱ͜ΖͲ͜Ζ͍ͬͯΔ͕ ͨͩͷσʔλΛ ׂೖͰهड़Ͱ͖ΔͷͰɺ هड़ྔ࠷খ
ݱ࣌Ͱͷײ • ্༷ͳ͍ঢ়ଶΛ࡞ΒͣʹࡁΉͨΊɺݎ࿚ • ΑΓෳࡶͳϫʔΫϑϩʔΛ࣮ͨ͠߹ಉ͡ߏʹऩ·Δɻೝෛՙ͕͍ • 3FTVMUܕʹΑΓܭࢉΛܨ͛ΒΕΔΑ͏هड़͢Δྑ͍ڧ੍ྗ͕ಇ͘ – ͨͩ͠ andThen().andThen().asyncAndThen.map()
͕͢͞ʹಡΈͮΒ͍ – )BTLFMMͷ EPه๏ɺ'ͷίϯϐϡςʔγϣϯࣜʹ૬͢Δͷ͕ཉ͍͠ŋŋŋ • ͷ٧Ίସ͑ͷهड़͕ͳ͍ͷ ͱͯ خ͍͠
;Γ͔͑Γ https://zenn.dev/mizchi/articles/oop-think-modern
·ͱΊ • ࣌ܥྻʹجͮ͘ঢ়ଶભҠͷએݴͱϑϨʔϜϫʔΫଆʹΑΔঢ়ଶભҠɺௐఀ – ϑϩϯτΤϯυɺͦͷͨΊͷϑϨʔϜϫʔΫͷ࣮͕ॆ࣮͍ͯ͠Δ • ʮએݴతϓϩάϥϛϯάʯͷϓϥΫςΟεɺݱ࣌Ͱܦݧతʹྑ͍ͷ – όοΫΤϯυ։ൃ͜ͷߟ͑ํʹऩᏑ͍ͤͯ͘͞ͷѱ͘ͳ͍ͷͰ ˠͬͯΈͨΒײ৮
– ྲྀߦΔ͔Ͳ͏͔Θ͔Γ·ͤΜ • ϑϩϯτΤϯυ όοΫΤϯυͷύϥμΠϜΪϟοϓΛগͳ͍͖͍ͯͨ͘͠
ัଊ • 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ԣணͯ͠ɺͬͯͳ͍ɻ ͪΌΜͱݕ౼͍ͯ͠ͳ͍
ࢀߟจݙ • 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