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

복잡한 상태관리도구 Svelte스럽게 만들기

kakao
PRO
December 08, 2022

복잡한 상태관리도구 Svelte스럽게 만들기

#Svelte #RxJS #상태관리 #함수형프로그래밍

Svelte에 맞는 상태관리 라이브러리를 만들기 위해 각종 상태관리 도구들을 이해하고 공부하면서
알게 된 반응형 프로그래밍과 함수형 사고 그리고 MVI 아키텍쳐에 대해 알아봅니다.

Svelte에 Rx 적용을 하고 FLUX 아키텍쳐를 재해석하여 만들어낸 상태관리도구를 소개합니다. 또한 이 라이브러리를 통해 개발하면서 마주쳤던 UI 프로그래밍을 힘들게 하는 비동기 처리, 중첩된 거대한 객체를 다루는 문제들을 어떻게 해결을 했는지 알려드립니다. 실제 파트원들도 사용해보면서 느낀 점 그리고 앞으로 어떤식으로 발전을 해나야가야할지 느꼈던 인사이트와 로드맵을 공유하고자 합니다.

발표자 : teo.yu
카카오엔터프라이즈 시니어 프론트엔드 개발자 테오입니다. 카카오워크의 캘린더 서비스를 개발하고 있습니다.

kakao
PRO

December 08, 2022
Tweet

More Decks by kakao

Other Decks in Programming

Transcript

  1. Copyright 2022. Kakao Corp. All rights reserved. Redistribution or public display is not permitted without written permission from Kakao.
    복잡한 상태관리도구 Svelte스럽게 만들기
    유용태 teo.yu


    카카오 엔터프라이즈
    if(kakao)2022
    Svelte + Rx를 이용한 Write less Code 상태관리 라이브러리 개발 이야기

    View Slide

  2. 상태관리 라이브러리를 만들게 된 배경


    Svelte에 Rx 더하기


    프론트엔드 아키텍쳐 다시 써보기


    실전과 부딪히며 라이브러리 확장/개선하기


    카카오워크웹파트 사용후기


    배운 점, 그리고 향후 로드맵

    View Slide

  3. 상태관리 라이브러리를 만들게 된 배경

    View Slide

  4. 우리는... 깐부 잖아
    \

    View Slide

  5. 우리는... 깐부 잖아
    \

    View Slide

  6. Svelte는?

    View Slide

  7. Svelte는 상태관리가 이미 프레임워크에 포함되어 있습니다.
    * Svelte Store는 Recoil, Jotai, Zustand와 같은 Atomic state management 랍니다.

    View Slide

  8. 전역 상태 / 공유 상태 제공


    Props Drill Problem 이슈 해결


    뷰 로직과 비즈니스 로직의 분리
    우리가 상태관리를 사용하는 이유
    👌

    View Slide

  9. View Slide

  10. 전역 상태 / 공유 상태 제공


    Props Drill Problem 이슈 해결


    뷰 로직과 비즈니스 로직의 분리


    상태가 언제 어떻게 왜 변화하였는지 추적하기


    상태 변화를 예측 가능하게 프로그래밍 하기


    디버깅을 용이하게 하기
    우리가 상태관리를 사용하는 이유
    🤔

    View Slide

  11. - 전역상태와 Props Drill Problem은 해결 되었지만...


    - 여전히 컴포넌트 간 복잡하게 얽히는 데이터 흐름과 구조


    - 데이터의 변화 추적와 디버깅이 점점 더 힘들어진다.


    - 이걸 전역 스토어로만 해결하는게 맞을까?


    - 전역 스토어는 전역 변수랑 뭐가 다르지?


    - 컴포넌트가 비즈니스 로직에 의존성이 있어도 되는 걸까?


    - 뷰 로직과 비즈니스 로직의 분리의 경계란?


    - 좋은 구조란 무엇인가?


    - 이런 고민을 한다는 것 부터가 일단 지금이 좋은게 아니라는 거겠지?
    복잡한 상태관리에 대한 고민과 문제들
    😵💫

    View Slide

  12. View Slide

  13. "Pet Hunt 선배님. 지금이 바로 그때 인것 같아요..."
    ೒۟झо ೙ਃೞѱ ؼ ٸݶ ঌѱ ؼ ѩפ׮. ೙ਃೠ૑ ഛपೞ૑ ঋਵݶ ೙ਃೞ૑ ঋणפ׮. ӒܻҊ Ӓ ೙ਃೡ ٸۄח Ѫ੉ ޥ૑ ঌѱ غ঻णפ׮

    View Slide

  14. Atomic 상태관리


    - 쉽고 단순하지만 복잡도가 올라갈수록 관리가 어려움.


    서버 기반 상태관리


    - 대부분은 좋으나 특이 케이스 대응이 어려움
    더 나은 Svelte용 상태관리 라이브러리를 만들자
    '내가 하는 개발은 엔터프라이즈 FE, 앞으로 점점 더 복잡해질 수 밖에 없다.'


    => 쉽고 편리함 보다 복잡하더라도 추적과 디버깅에 용이한 더 체계적인 상태관리 라이브러리가 필요하다!

    View Slide

  15. 그렇지만 Svelte + Redux를 쓰고 싶지는 않았습니다.

    View Slide

  16. Write less Code
    Svelte의 철학

    View Slide

  17. Flexibility Simple
    복잡하지만 유연하게 그렇지만 문법은 단순하게

    View Slide

  18. Redux에서 대안을 찾았다! Rx

    View Slide

  19. Redux에서 대안을 찾았다! Rx
    "Rx 안에서 Redux를 재구현하는 것은 그리 어렵지 않습니다."

    View Slide

  20. View Slide

  21. Svelte에서는 RxJS를 공식 지원합니다.
    🥳

    View Slide

  22. Svelte에 Rx 더하기

    View Slide

  23. Rx란? 반응형 프로그래밍을 할 수 있도록 해주는 API
    RxJS is a library for reactive programming using Observables,


    to make it easier to compose asynchronous or callback
    -
    based code.

    View Slide

  24. 반응형 프로그래밍? 스프레드 시트!

    View Slide

  25. Javascript는 반응형이 아니다.
    <-
    A의 값을 바꿔도 B, C는 그대로이다.

    View Slide

  26. 그래서 Rx에서는 이런식으로 반응형을 만들어줍니다.
    A
    B C
    1 2 3
    A + 1 A + B
    1..2..3
    2..3..4 3..5..7

    View Slide

  27. Svelte도 반응형을 지원하는데 왜 Rx인가요?
    심지어 훨씬 더 단순하고 쉬워보이는데요?

    View Slide

  28. Rx는 시간도 선언적으로 다룰 수 있게 해준다.
    트리플 클릭 구현하기
    7초동안 응답이 없으면 에러처리


    에러가 발생하면 3초 후 다시 시도


    3번 실패하면 에러
    🤕
    setTimeout?
    callback?
    promise?

    View Slide

  29. Rx는 시간도 선언적으로 다룰 수 있게 해준다.
    😲

    View Slide

  30. Rx는 시간도 선언적으로 다룰 수 있게 해준다.
    👍

    View Slide

  31. Rx에 있는 유용한 Operator들
    - map /
    fi
    lter / take


    - distinctUntilChanaged


    - bufferCount


    - debounce


    - throttle


    - retry


    - timeout


    - mergeMap / switchMap / exhaustMap / concatMap


    - 그밖에도 정말 좋은 Operator들이 있어요!
    🤩

    View Slide

  32. 그렇지만 Rx도 굉장히 코드가 장황합니다.


    Svelte와 오리지널 Rx는 물론 궁합이 좋지만


    Svelte Store와 결합하여 훨씬 단순한 Rx를 만들고 싶었어요.

    View Slide

  33. Svelte와 Rx의 퓨전!
    RxJS Svelte Store
    복잡하지만 풍부한 기능 단순하지만 단순한 기능
    svelte와 일부만 연동 svelte와 완전히 연동
    New Rx!
    단순하지만 풍부한 기능
    svelte와 완전히 연동
    +auto
    -
    unsubscribe
    +dot chain 지원
    +Typescript 지원

    View Slide

  34. Rx와 Svelte의 아름다운 만남 ❤

    View Slide

  35. 프론트엔드 아키텍쳐 다시 써보기

    View Slide

  36. FLUX로 부터 시작된 상태관리 아키텍쳐

    View Slide

  37. Middleware
    Event Handler
    Dispatch
    UI
    State
    Reducer
    API
    Dispatch
    그리고 Redux 아키텍쳐
    Action

    View Slide

  38. 아키텍쳐는 정말 좋다고 생각했습니다.


    문제는 장황한 문법과 학습곡선이었죠!

    View Slide

  39. 그래서 조금 더 직관적으로 설명이 가능한 아키텍쳐와


    쉬운 문법으로 재작성하고 싶었습니다.

    View Slide

  40. 영감을 받은 Rx 아키텍쳐
    Cycle.js


    MVI 아키텍쳐
    NgRx


    Rx + Redux

    View Slide

  41. Computer
    Human
    Ouput Input
    UI가 함수라면? (input
    ->
    output)

    View Slide

  42. Model
    Human
    View ?
    Model - View - ?

    View Slide

  43. Model
    Human
    View Intent
    Model - View - Intent

    View Slide

  44. MVI 아키텍쳐 (Model - View - Intent)
    - Unidirectional cycle of data


    - Non
    -
    blocking


    - Immutable state


    ->
    Functional Paradigm
    Model()
    User()
    View() Intent()

    View Slide

  45. User(View(Model(Intent(User(screen))))


    screen
    ->
    event
    ->
    action
    ->
    state
    ->
    screen


    type User = (screen) => event


    type Intent = (event) => action


    type Model = (action) => state


    type View = (state) => screen
    Model(action)
    User(screen)
    View(state) Intent(event)
    action
    event
    state
    screen

    View Slide

  46. MVI 아키텍쳐 관점에서 Svelte + Redux 바라보기
    Component


    Action


    Reducer


    Store
    Model(action)


    Reducer
    User(screen)
    View(state)


    Component
    Intent(event)


    Event Handler
    on(action)
    event
    store.update()
    screen
    dispatch(action)
    store.subscribe()

    View Slide


  47. Action
    Model
    아키텍쳐 다시 써보기
    Reducer
    Store
    dispatch
    on
    writeTo
    subscribe
    Intent
    View
    Component State Management

    View Slide

  48. 카운터 샘플
    0
    + - Reset

    View Slide

  49. 카운터 샘플 코드

    View Slide

  50. 카운터 샘플 코드

    View Slide

  51. 카운터 샘플 코드

    View Slide

  52. 카운터 샘플 코드

    View Slide

  53. 카운터 샘플 코드

    View Slide

  54. 카운터 샘플 코드

    View Slide

  55. Adorhable Store


    action()


    dispatch()


    on()


    reducer()

    View Slide

  56. Action과 Reducer는 왜 쓰는 건가요?
    🤔
    Action?
    Reducer?
    그냥 바로 값을 수정하면 안되나요?

    View Slide

  57. 카운터 샘플 코드
    1. 컴포넌트와 상태관리 코드를 완전히 분리할 수 있어 화면 변화에 유연해집니다.
    또한 프로그래밍을 사용자의 행동을 중심으로


    생각할 수 있도록 해줍니다.

    View Slide

  58. 카운터 샘플 코드
    2. 상태가 변경되는 코드를 하나의 모듈에 고립시킬 수 있습니다.

    View Slide

  59. 카운터 샘플 코드
    3. 그리고 언제, 왜 데이터가 어떻게 변했는지 추적이 가능합니다.
    #0 _증가, count: 0
    ->
    1


    #1 _증가, count: 1
    ->
    2


    #2 _감소, count: 2
    ->
    1


    #3 _초기화, count: 1
    ->
    0


    #4 _증가, count: 0
    ->
    1

    View Slide

  60. 카운터 샘플 코드
    서비스가 복잡해지고 관리해야 할 상태가 늘어나도


    화면변화 대응에 유연하고


    상태변화 추적에 용이하며 디버깅하기 쉬운 개발을 할 수 있다.

    View Slide

  61. 전역 상태 / 공유 상태 제공


    Props Drill Problem 이슈 해결


    뷰 로직과 비즈니스 로직의 분리


    상태가 언제 어떻게 왜 변화하였는지 추적하기


    상태 변화를 예측 가능하게 프로그래밍 하기


    디버깅을 용이하게 하기
    우리가 상태관리를 사용하는 이유
    😎

    View Slide

  62. 실전과 부딪히며 라이브러리 확장/개선하기

    View Slide

  63. "... Redux는 reducer가 어려운게 아니야


    middleware가 어려운거지..."

    View Slide

  64. 서버상태
    비동기
    async server state
    테스트
    test
    웹 프론트엔드 프로그래밍을 어렵게 만드는 요소

    View Slide

  65. 비동기 코드를 다루기가 너무 어렵다.

    View Slide

  66. - 프로그램이 작성한 순서대로 동작하지 않는다


    - 결과를 예측할 수가 없고 성공과 실패, 로딩중과 캐시 등의 다양한 분기를 가진다.


    - 그렇지만 비동기 결과의 순서를 조정해야 할 필요가 생긴다.


    => 낮은 예측성, 강한 결합도를 가진 복잡한 코드


    => 유연하지 못하고 디버깅이 어려운 코드


    문제는... UI가 곧 비동기라는 것
    비동기 프로그래밍이 어려운 이유
    A
    B
    C
    D

    View Slide

  67. - 같은 구성이라도 Push의 관점으로 바라보면 작업이 간단해집니다.


    - 모듈 내 역할만 수행하고 생성한 데이터와 제어권을 다른 모듈에게 위임하는 방식


    - 하나의 거대한 관리자 모듈이 필요없기 때문에 모듈의 의존성이 낮고 간결해진다.
    복잡한 비동기 Push 방식으로 풀어보자!
    A
    C
    D
    B

    View Slide

  68. 의존성과 결합도가 높아지는 Pull 방식

    View Slide

  69. 느슨한 결합이 가능한 Push 방식
    제어의 역전으로 느슨한 결합


    결합도 down, 응집도 up

    View Slide

  70. Pull 방식에서 비동기 처리와 try ~ catch
    결합도가 높아져서


    Messive Function이 될 확률이 높다.

    View Slide

  71. Intent
    Service
    Effect
    Action
    Action
    Reducer
    REQUSET
    SUCCESS
    FAILURE
    비동기를 Action으로 Action을 Reducer로...

    View Slide

  72. Push 방식으로 모듈화 하기 (Story)
    Push 방식으로 자유롭게 코드를 배치할 수 있어 모듈별로 응집도를 높일 수 있다.

    View Slide

  73. 비동기에 액션을 결합하면


    코드를 원하는대로 배치할 수 있다.
    dispatch(Action.REQUEST, promise)
    ->
    SUCCESS, FAILTURE

    View Slide

  74. Rx의 Observable도 연동하여


    스트림 비동기 동작도 간단하게 연동
    dispatch(Action.START, observable)
    ->
    NEXT, ERROR, COMPLETE

    View Slide

  75. Adorhable Store


    action()


    dispatch()


    on()


    reducer()


    story()

    View Slide

  76. 아키텍쳐 확장하기
    component
    Pure Cycle Story(= Side Effect)

    View Slide

  77. 서버상태관리


    엔티티와 거대한 중첩된 객체 다루기

    View Slide

  78. 중첩된 복잡한 상태를 불변성을 유지하며 다루는 기존 방법
    🤢

    View Slide

  79. Array에서 불변성을 다루기 위한 코드

    View Slide

  80. immer.js, imutable.js


    불변성과 중첩된 객체를 다루기 위한 라이브러리

    View Slide

  81. Firebase를 연동하면서 배운 것들


    - path를 이용한 중첩된 객체 다루기


    - path별로 Atom이 될 수 있다!


    - Array보다 key
    -
    value Object로 다루기


    - Collection을 Object.values와 sort로 다룬다.


    ->
    path를 기반으로 하는


    Reactive한 database API를 만들자.


    View Slide

  82. database(path)

    View Slide

  83. database(path)
    단점.. String 방식

    View Slide

  84. GraphQL에서 영감을 얻다.


    스키마를 중심으로 개발하자


    query와 mutation을 나눠서 생각하자.


    query = (props) => selector


    gql과 같이 Query를 만들어보자

    View Slide

  85. AQL: SELECT API, UPDATE API

    View Slide

  86. 상태관리 전체 Data Flow

    View Slide

  87. 언뜻 복잡해보이는 체계를 가지고 있으나


    데이터가 한 방향으로 선형적으로 움직이고 있기 때문에


    Input - Ouput을 기반으로


    어느 공정에서 어디를 수정해야하는 더 찾기가 쉬워졌습니다.

    View Slide

  88. Adorhable Store


    action()


    dispatch()


    on()


    reducer()


    story()


    database()


    query()
    type Collection


    createStore()


    SELECT()


    UPDATE()


    GET()


    DELETE()

    View Slide

  89. 카카오워크 웹파트 사용 후기

    View Slide

  90. 어떤 점이 제일 좋았는지 물어봤습니다.


    View Slide

  91. 좋았던 점


    데이터를 다루는 흐름을 자연스럽게 이해할 수 있었다.


    내가 개발을 할 때 지금 어느 단계의 데이터를 어떻게 만들고 전달해야하는 지 알 수 있다.


    자동으로 로깅이 되어 디버깅을 하기에 수월했다.


    문제가 발생했을 때 언제 어떻게 데이터가 수정이 되었는지 알수 있어서 디버깅 하기에 좋았다.


    복잡하게 생각하지 않을 수 있게 해줘서 좋았다.


    이미 만들어진 체계 위에 새로운 기능을 확장하거나 수정하기에 용이했다.

    View Slide

  92. 후기, 그리고 향후 로드맵

    View Slide

  93. 배운 것
    복잡함에는 이유가 있다.


    개념은 복잡해도 문법은 단순하게 만들 수 있다.


    복잡한 구조도 단순한 관점으로 바라볼 수 있다.


    체계가 곧 컨벤션이 될 수 있다.


    계층을 더 잘게 만들어 둘 수록 복잡해지지만 더 단순하게 생각하게 만들어준다.


    계층이 곧 컨벤션이 되고 이는 클린코드로 이어진다.

    View Slide

  94. 현재 개발하고 있는 것들


    Devtools


    User Input Database


    LocalStorage, IndexedDB와 같은 스토리지 기반 자동 연동


    낙관적 업데이트를 위한 트랙잭션 API


    React에서도 쓸 수 있도록


    Test Library와 TDD

    View Slide

  95. 전역 상태 / 공유 상태 제공


    Props Drill Problem 이슈 해결


    뷰 로직과 비즈니스 로직의 분리


    상태가 언제 어떻게 왜 변화하였는지 추적하기


    상태 변화를 예측 가능하게 프로그래밍 하기


    디버깅을 용이하게 하기


    ...


    Write less 하게 계속 만들어 가 보겠습니다!!
    끝으로... 상태관리를 만들고 싶었던 이유
    😎

    View Slide

  96. 감사합니다.
    😄

    View Slide