유효한 조합이 컴파일 타임에 없을 때
·
<frontend>/타입 | 도메인
들어가며저는 불가능한 상태를 타입으로 못 만들게 하는 걸 좋아합니다. 태그드 유니온으로 케이스를 닫아두면 컴파일러가 대신 지켜주니까요. 근데 조건끼리 서로 의존하는 조합 상품 카탈로그 타입을 설계하다가 이게 안 먹히는 경우를 만났습니다. 저희 유전변이해석 도메인은 낯서니까 매우 비슷한 애플스토어 맥북 카탈로그로 재구성했습니다. 상품 주문 화면에서 모듈간 의존이 놀랍도록 유사한 레퍼런스더라구요상품이 조합으로 무한정 늘어난다애플은 "맥북"이라는 한 덩어리로 팔지 않습니다. 네오, 에어, 프로 세 라인이 있고, 같은 라인 안에서도 화면 크기·칩·메모리·저장장치·색상이 다르면 다른 상품입니다. 견적 내면서 하나씩 고르는 그 조합마다 가격이 따로 붙으니까요. 구성 단위로 파는 만큼 상품 가짓수는 곱셈으로 늘어납니..
콜오브듀티 모던워페어 4 보안요구사항 해결하기(MSI B450 + AMD)
·
<etc>
콜 오브 듀티에서 TPM 2.0도 켜고 Secure Boot도 켰는데 계속 "BIOS 펌웨어 업데이트 필요"가 뜨고 캠페인만 되는 사람들을 위한 글.결론부터: fTPM 펌웨어 버전이 문제고, 베타 BIOS로 올려야 해결된다.내 환경항목사양메인보드MSI B450 GAMING PRO CARBON MAX WIFI (MS-7B85)CPUAMD Ryzen 7 3700XOSWindows 10 22H2증상콜옵 베타에서 "BIOS 펌웨어 업데이트 필요", 캠페인만 플레이 가능핵심만 먼저Win + R → tpm.msc → 우측 하단 TPM 제조업체 정보 → 제조업체 버전 확인.AMD 기준, 세 번째 자리가 0이면 콜옵이 거부한다.버전판정3.94.0.5거부3.37.0.5거부3.94.2.5통과숫자가 크고 작고는 상관없다. ..
closure of operations(닫힌 연산) FE 인가정책에 적용하기
·
<frontend>/타입 | 도메인
들어가며주니어 면접 단골 질문으로 나오는 그 클로저 아닙니다에릭 에반스의 DDD 10장(Supple Design)에 closure of operations, 닫힌 연산이라는 패턴이 있습니다."Where it fits, define an operation whose return type is the same as the type of its argument(s)." 맞는 자리라면, 반환 타입이 인자 타입과 같은 연산을 정의하라.연산에 들어가는 타입과 나오는 타입을 같게 하라는 겁니다. 사실 우리 모두 초등학교 때부터 쓰던 성질이에요.1 + 2 // 3 — 정수 + 정수 = 정수(1 + 2) + 3 // 6 — 결과를 그대로 다음 덧셈에((1 + 2) + 3) ..
데이터 오염을 경계에 격리하기
·
<frontend>/컴포넌트 설계
들어가며 기능 개발을 하다 보면 이런 공지를 받습니다. "API 응답에 필드 하나 추가했어요. 옵셔널이라 기존 동작엔 영향 없습니다."저희는 파이프라인이 복잡하고 오래 운영된 서비스라 레거시 데이터가 많았고, backfill(나중에 생긴 규칙을 과거 데이터에 채워넣는 DB 작업)보다 API 응답 옵셔널 추가가 합리적인 상황이 자주 나왔어요. 그래서 이런 공지도 자주 받았고, 매번 별생각 없이 넘겼습니다.근데 몇 주 지나서 컴포넌트 코드를 열어보면, 그 영향 없는 필드가 여기저기 분기를 뿌려놓고 있더라고요.옵셔널인데 왜 문제가 생겨요?실제로 동료가 물었던 질문입니다. 옵셔널 추가는 API 스펙 관점에선 하위호환이 맞아요. 문제는 스펙이 아니라 타입이 앱 안에서 하는 일입니다. 서버가 필드를 옵셔널로 추가하..
인증 스텝 페이지 경계 쪼개기
·
<frontend>/컴포넌트 설계
들어가며회원가입 플로우에 MFA 설정 단계를 추가하게 됐습니다.기존 회원가입은 여러 스텝을 거치는 구조였습니다. 이메일 입력, 인증 코드 확인, 프로필 입력, … 클라이언트가 "지금 몇 번째 스텝이냐"를 useState로 들고 있고 화면이 순차적으로 이어지는, 우리가 흔히 "퍼널"이라고 부르는 그런 구조요.여기에 MFA 설정 단계를 끼워 넣어야 했습니다. TOTP를 설정하고, recovery code를 확인시키고, 완료 처리까지요. 이것도 스텝이 순차적으로 이어지니까, 기존 회원가입 퍼널에 스텝 몇 개 추가하듯이 자연스럽게 MfaSetupFlow라는 페이지 안의 상태 기계로 묶었습니다.type MfaSetupStep = | { step: "totpSetup" } | { step: "emailSetup..
구현 말고 질문에 이름 붙히기
·
<frontend>/타입 | 도메인
들어가며여러 종류의 아이템을 다루다 겪은 일인데, 실제 도메인은 좀 낯서니까 커머스 대시보드 예시로 바꿔서 이야기해보겠습니다.활동 내역을 다루는 화면을 만들고 있었습니다. 활동에는 여러 종류가 있고, 각 종류마다 선택 상태를 key로 관리해요. 주문(order)을 예로 들면 key 구조가 이렇습니다.order key: userId+orderIdline key: userId+orderId::skuline key가 order key를 prefix로 포함하는 구조입니다. 그래서 "이 아이템이 이 주문에 속하는가"를 startsWith 하나로 판별할 수 있어요.const isChecked = selected.some(item => orderList.some(order => item.key.startsW..
토스 Frontend Fundamentals 모의고사 후기 (2회차)
·
<frontend>/toss ff 모의고사
1회차 후기에 이어서 2회차 모의고사 후기를 쓴다.1회차에서도 강조됐던 내용인데, 예측 가능한 코드를 짜야한다는주제가 이번에도 반복적으로 나왔다사람 뇌는 코드를 "이해"하지 않는다사람 뇌가 코드를 읽는 방식은 생각보다 단순하다. 한 줄 한 줄 의미를 분석하는 게 아니라, 함수명이나 구조를 보고 "이렇게 동작하겠지?"를 먼저 예측한다. 그 예측이 맞으면 넘어가고, 틀리면 그때서야 내부를 파고든다.문제는 예측이 틀렸을 때다. 인지 강도가 급격하게 올라간다. 결국 가독성이라는 건 코드가 얼마나 "뻔하게" 읽히느냐의 문제다.호출처의 관심사를 모르게 추상화한다이번에 가장 신선했던 관점이다.DatePicker 컴포넌트를 하나 만든다고 생각해보자. 이 컴포넌트에 date와 setDate를 prop으로 내려보내면 어떻..
원자적 action으로 훅의 확장성 확보하기
·
<frontend>/컴포넌트 설계
들어가며vscode의 탭 바처럼 동작하는 대시보드를 만들고 있었습니다. 파일을 열면 탭이 생기고, 클릭하면 전환되고, X로 닫는 그 구조요.대시보드에는 화면 종류가 몇 가지 있습니다. 항상 존재하는 기본 탭인 home이 있고, 새 인사이트를 작성하는 new는 사이드바에서 "새 페이지 만들기"를 누르면 열립니다. 작성이 끝난 개별 인사이트 페이지는 insight-{id}인데, new에서 저장하면 insight-42 같은 탭으로 교체됩니다. 그 밖에 특정 태그에 속한 인사이트 목록 tag-{id}, 그리고 휴지통 trash가 있습니다.이 탭 상태를 어디에 저장할까요? React state에 넣으면 간단하지만 새로고침하면 날아갑니다. 그래서 공유 기능도 넣을 겸 URL query string을 single s..
Branded Type String Key Factory 만들기
·
<frontend>/타입 | 도메인
들어가며지난 글에서 Branded Type으로 유틸 함수 사용을 강제하는 방법을 다뤘습니다. 지난 글과 마찬가지로 실제 도메인은 낯서니까 커머스 대시보드 예시로 바꿔서 이어가겠습니다. getOrderAnchorId, getRefundAnchorId 같은 개별 함수를 만들어서 activity type마다 올바른 ID 포맷을 보장했죠.근데 activity type이 6개가 넘어가면서 함수가 너무 많아졌고, 어떤 게 있는지 찾기가 힘들어졌습니다. 자동완성으로 get만 쳐도 수십 개가 뜨는데, 그 중에서 anchor id 관련 함수만 골라내는 것도 일이었거든요.마침 예전에 쓰던 쿼리 키 팩토리 패턴이 떠올랐습니다. 쿼리 키처럼 일괄 invalidate할 일은 없지만, 관련 함수를 하나의 객체로 묶는다는 아이디어..
Branded Type으로 유틸함수 강제하기
·
<frontend>/타입 | 도메인
들어가며유전진단 SaaS에서 여러 종류의 아이템을 다루다 겪은 일인데, 실제 도메인은 좀 낯서니까 커머스 대시보드 예시로 바꿔서 이야기해보겠습니다.대시보드에서 활동 내역(activity) 아이템을 클릭하면 해당 row로 스크롤되는 기능이 있었습니다. URL 해시 기반 네비게이션인데, 사용자 입장에서의 흐름은 이렇습니다.테이블 row에는 id="ORDER----" 속성이 붙어 있고(strategy), activity card에서 코멘트 버튼을 누르면 "ORDER----"가 포함된 메시지가 채팅에 추가됩니다(message). 나중에 채팅에서 그 메시지의 "Go to Activity" 링크를 누르면 #ORDER---- 해시로 이동해서 row로 스크롤되고요(navigation).row에 부여한 ID를 메시지에 ..
컴포넌트 분기를 상위로 밀어내기
·
<frontend>/컴포넌트 설계
들어가며이전 글에서 로직 계층을 내려서 단일 관심사를 챙기는 이야기를 했었습니다. 여러 주제가 섞인 컴포넌트를 분리하고, 각 컴포넌트가 자기 로직만 가지도록 만드는 거였죠.이번엔 반대 방향입니다. 분기를 상위로 올려서 단일 관심사를 챙기는 이야기를 해보겠습니다. 여기서 단일 관심사란 곧 높은 응집도, 그러니까 하나의 컴포넌트가 하나의 맥락에 필요한 것만 모아 둔 상태를 말합니다. 근데 컴포넌트 안에서 강하게 결합된 로직들을 어떻게 분리할 수 있을까요?위에서 풀수록 아래가 단순해집니다React 컴포넌트는 단방향 트리 구조입니다. 데이터와 제어 흐름이 위에서 아래로만 흐르죠. // 여기서 결정하면 // 여기는 단순해진다즉 상위에서 복잡도를 해소할수록 하위가 단순해집니다. Typ..
단일 관심사 컴포넌트
·
<frontend>/컴포넌트 설계
들어가며이전 글에서 훅 추상화를 아토믹하게 쪼개는 이야기를 했었습니다. 몬스터 훅 하나 대신 URL 필터 동기화, 날짜 범위 처리 같은 로직을 각각 분리해서 조합해 쓰는 방식이었죠. 덕분에 각 로직이 한 가지 일만 해서 파악하기 쉬웠습니다.근데 막상 컴포넌트를 보면 얘기가 좀 달랐습니다. 상품 상세 페이지를 만든다고 해보겠습니다.function ProductPage({ productId }) { // 상품 정보 관련 const { data: product } = useProductQuery(productId); // 장바구니 관련 const [quantity, setQuantity] = useState(1); const { mutate: addToCart } = useAddToCart(); /..