안녕하세요! 그로스로그 입니다!😊 🌱
답을 정해주지 않되, 판단할 수 있게 재료를 갖춰 넘긴다. 4기 마지막 회차 세 편이 나란히 그 이야기였습니다.
성장일지는 멤버들이 2주에 한 번씩 자신의 성장을 기록하는 활동이에요. 매 회차 운영진이 모든 글을 읽고, 그중 특히 마음에 남은 글을 함께 골라 큐레이션합니다.
4기 마지막 회차입니다. 기획 문서 한 편, 아키텍처 정리 한 편, 그리고 작은 프로젝트 하나가 올라왔어요.
01
🏛️ The second project this year
박석희 님
한국현대사 검색 앱을 만들기로 한 기록입니다. 아직 시작 단계예요. "진행되는대로 본 게시글에 진행 상황을 업데이트할 예정"이라고 적혀 있습니다.
앞부분이 왜 만드는가입니다. 정치 갈등 때문에 소모적 논쟁과 왜곡이 많은 영역이라, 객관적 사료와 데이터로 정리하고 싶었다고요.
뒷부분이 이 글의 백미입니다. "나무위키와 의도적으로 다르게 설계해야 할 UI/UX 원칙 10가지." 기존 서비스와 어떻게 갈라설지를 열 개로 정리했어요.
6회차·7회차에 이어 세 번째로 소개하는 저자입니다. 이번엔 직접 만드는 이야기예요.
조금 더 들어가 보면
먼저 출발점입니다.
그리고 왜 자기가 할 수 있다고 생각했는지도 밝힙니다.
도메인 지식이 있는 영역을 골랐습니다. 7회차 김석현 님 회고에서 "도메인에 대한 이해는 필수적"이라며 구현이 후순위가 됐다던 그 이야기와 같은 자리예요. 이 분은 이미 도메인을 아는 쪽에서 출발합니다.
그리고 6회차에서 본 그 고민이 여기서 매듭지어집니다.
6회차 글에서 이분은 "AI에 종속되는 거지, 내가 문제를 해결하는 게 아니다"라며 기초를 쌓는 쪽을 골랐어요. 그런데 지금은 AI로 만들고 있습니다.
말이 바뀐 게 아닙니다. 순서가 그랬던 거예요. 기초를 좀 쌓은 다음에, 자기가 아는 영역에서 도구를 씁니다. 6회차의 표현대로 "깊이 있게 도움을 구하고 체계적인 지시를 내릴 수 있도록" 하는 상태에 가까워진 거죠.
📚 자료 목록이 이 프로젝트의 성격을 말한다
기술 계획은 짧습니다. 챗GPT API + RAG 파이프라인.
그런데 자료 출처 목록이 깁니다.
RAG 프로젝트의 성패는 대개 여기서 갈립니다.
RAG는 가지고 있는 자료 안에서 찾아 답하는 구조예요. 그러니까 자료가 답의 한계입니다. 아무리 좋은 모델을 써도 없는 자료는 못 찾아와요.
그런데 대부분의 RAG 프로젝트는 모델과 파이프라인 이야기가 길고 자료 이야기가 짧습니다. 이 글은 반대예요. "1차 자료들을 수집하러 다닐 생각"이라고 발로 뛰겠다고 적었습니다.
그리고 "1차 자료"라는 구분이 나오는 게 역사 전공자다운 지점입니다. 원본 기록과 그걸 해석한 연구는 다른 무게를 갖거든요.
🧭 열 개의 원칙 — 나무위키와 갈라서기
이 글의 본론은 UI/UX 원칙 열 개입니다. 그런데 비교 대상을 명확히 정한 게 이 문서를 좋게 만들었어요.
깎아내리지 않고 시작합니다. 그리고 "이 영역에서는"이라는 조건을 붙였어요. 3회차 김종진 님이 퀴즈 플랫폼을 비교할 때와 같은 방식입니다.
원칙들을 관통하는 축이 몇 개 보입니다.
첫째, 읽는 곳이 아니라 묻는 곳으로.
문서를 찾아 스크롤하는 대신 질문 입력이 시작점입니다. "이승만은 독재자였나?", "1948년 기준으로 평가가 어떻게 갈렸나?" 같은 예시를 들었어요.
둘째, 출처를 전면에.
각주 [1][2] 대신 문단 옆에 출처 카드를 두자고 합니다. 그리고 표시할 것을 구체적으로 나열해요. 저자 / 소속 / 발행 기관 / 자료 성격(1차·2차 사료).
셋째, 수렴하지 않기.
관점 A(교과서·주류 학계) / 관점 B(비판적 연구) / 관점 C(동시대 자료 해석)를 나란히 둡니다.
⚠️ AI 앱이 스스로를 의심하는 설계
가장 인상적인 원칙이 다섯 번째입니다.
그리고 UX 장치를 제안해요. "이 답변은 ○○ 자료에 근거함" 표시, "해석이 갈리는 쟁점임" 뱃지, 확정적 표현 최소화.
AI 서비스를 만들면서 AI의 강점을 약점으로 본 겁니다.
생성 모델은 매끄러운 문장을 잘 씁니다. 그런데 매끄러운 문장은 틀렸을 때도 매끄러워요. 근거가 부족한 답도 확신에 찬 문장으로 나옵니다. 그래서 읽는 사람이 의심할 계기를 못 갖습니다.
그래서 일부러 확신을 덜어내는 장치를 UI에 넣자는 겁니다. 뱃지를 붙이고, 근거를 옆에 두고, 표현 수위를 낮춰서요.
여섯 번째 원칙도 같은 결입니다.
"이 답변은 2026년 2월 기준 자료로 생성됨", "2000년대 이전 연구 / 이후 연구 구분"
"역사적 평가는 고정값이 아니라 시간에 따라 변하는 값"
답에 유효기간을 붙이는 설계예요.
🤝 그리고 마지막 원칙
열 번째를 "가장 중요한 원칙"이라고 적었습니다.
AI 앱: "이런 근거들이 있다. 판단은 당신의 몫이다"
"앱은 결론을 내리지 않는다. 판단 가능한 상태를 만들어줄 뿐"
결론을 내리지 않겠다는 선언입니다.
그런데 이게 아무 말도 안 하겠다는 뜻이 아니에요. 앞의 아홉 개 원칙이 전부 판단할 수 있게 만드는 장치입니다. 출처를 보여주고, 관점을 나란히 놓고, 시간 기준을 밝히고, 단계적으로 펼쳐주는 것 전부요.
정리는 하되 결론은 안 낸다. 이게 이 문서 전체의 태도입니다.
큐레이터 노트
만들기 전에 기준부터 세운 문서라서 뽑았습니다.
개인 프로젝트는 대개 일단 만들고 봅니다. 그런데 이 글은 코드 한 줄 전에 "기존 서비스와 무엇이 달라야 하는가"를 열 개로 정리했어요. 그리고 각 원칙마다 📌 UX 원칙으로 한 줄 요약까지 붙였습니다.
비교 대상을 정한 게 이 문서를 구체적으로 만들었어요. 그냥 "좋은 UX"를 논했다면 뻔한 이야기가 됐을 텐데, 나무위키와 뭐가 달라야 하나로 물으니 답이 날카로워집니다. 차별점을 먼저 정의하고 시작하는 것은 제품 기획의 기본이기도 하고요.
민감한 영역을 다루면서 그걸 알고 있다는 것이 특히 좋았습니다. 한국현대사는 답을 내면 반드시 누군가는 동의하지 않는 주제예요. 그래서 답을 내지 않는 구조를 택했습니다. 회피가 아니라 그 영역에 맞는 설계 판단이에요.
"진행되는대로 본 게시글에 진행 상황을 업데이트할 예정"이라고 적은 것도 좋았습니다. 완성된 다음에 쓰는 게 아니라 시작 지점에 말뚝을 박아둔 기록이에요. 나중에 이 글을 다시 보면 처음에 뭘 하려 했는지가 정확히 남아 있을 겁니다.
02
📮 Cosmic Python 제2부: 이벤트 기반 아키텍처
송영록 님
『Architecture Patterns with Python』(Cosmic Python) 2부를 정리한 글입니다. 33분 분량이에요.
다루는 패턴이 넷입니다. 도메인 이벤트 · 메시지 버스 · CQRS · 의존성 주입.
그런데 문제 상황에서 출발합니다. "재고 할당에 실패하면 이메일을 보내야 하는데, 이 로직을 어디에 넣어야 할까?" 안티패턴 셋을 차례로 보여주고, 그다음 해법으로 갑니다.
8회차 AOP 글에 이어 두 번째로 소개하는 저자입니다. 이번엔 책 정리인데, 8회차와 겹치는 문제의식이 있어요.
조금 더 들어가 보면
앨런 케이의 말로 시작합니다.
객체지향을 만든 사람이 '객체'라는 이름을 후회한다는 인용이에요. 이 글 전체가 그 말을 풀어냅니다.
📧 "이메일 로직을 어디에 넣지?"
문제 제기가 아주 구체적입니다.
"the goop around the edge" — 번역하기 어려운 표현인데, 가장자리에 눌어붙은 것들쯤 됩니다. 보고서, 권한 체크, 알림 발송 같은 것들요.
8회차 AOP 글에서 횡단 관심사라고 불렀던 그것과 같은 계열입니다. 본론이 아닌데 없으면 안 되는 것들이에요.
그리고 안티패턴을 셋 보여줍니다.
안티패턴 1 — 웹 컨트롤러에 넣기
try:
batchref = services.allocate(line, uow)
except (model.OutOfStock, services.InvalidSku) as e:
send_mail("out of stock", "stock_admin@made.com", f"{line.orderid} - {line.sku}")
return {"message": str(e)}, 400
안티패턴 2 — 도메인 모델에 넣기
except StopIteration:
email.send_mail("stock@made.com", f"Out of stock for {line.sku}")
raise OutOfStock(...)
email.send_mail이 들어가는 순간, 도메인 모델이 인프라에 의존하게 된다."재고 계산 로직이 SMTP 서버를 알게 되는 상황입니다. 테스트하려면 메일 서버를 흉내내야 하고요.
안티패턴 3 — 서비스 레이어에 넣기
try:
batchref = product.allocate(line)
uow.commit()
return batchref
except model.OutOfStock:
email.send_mail("stock@made.com", f"Out of stock for {line.sku}")
raise
셋 중 가장 나아 보이는데도 문제라고 짚은 게 좋았습니다. 실제로 실무에서 이 형태를 제일 많이 보거든요.
✂️ "그리고"가 나오면 SRP 위반
그리고 판별 기준이 나옵니다.
아주 실용적인 검사법입니다.
"이 함수는 재고를 할당합니다"는 한 가지 일이에요. "이 함수는 재고를 할당하고, 실패하면 이메일을 보냅니다"는 두 가지죠.
그리고 왜 문제인지도 명확합니다.
allocate() 함수를 수정해야 한다면? 그건 잘못된 설계다."알림 방식이 바뀌었는데 재고 로직을 건드리게 되는 것 — 이게 결합입니다.
📝 해법 1 — 사실만 기록하기
이벤트를 아주 단순하게 정의합니다.
from dataclasses import dataclass
class Event:
pass
@dataclass
class OutOfStock(Event):
sku: str
메서드가 하나도 없습니다. 그냥 데이터예요.
그리고 도메인 모델이 이걸 쌓아둡니다.
class Product:
def __init__(self, sku, batches, version_number=0):
...
self.events = [] # 이벤트를 쌓아두는 리스트
def allocate(self, line: OrderLine) -> str:
try:
batch = next(b for b in sorted(self.batches) if b.can_allocate(line))
batch.allocate(line)
self.version_number += 1
return batch.reference
except StopIteration:
self.events.append(events.OutOfStock(line.sku))
return None
여기가 이 글의 전환점입니다.
Product는 이제 "재고가 없다"는 사실을 적어둘 뿐이에요. 그래서 뭘 해야 하는지는 모릅니다. 이메일을 보낼지, SMS를 보낼지, 슬랙에 올릴지, 아무것도 안 할지요.
판단을 자기가 안 하고 넘긴 겁니다. 대신 판단에 필요한 정보(sku)는 담아서 넘겼어요.
그리고 2회차에서 본 그 원칙 — "예외를 흐름 제어로 쓰지 말 것" — 이 여기서도 나옵니다. 2회차 박지훈 님 글에서 try/catch로 존재 여부를 판단하던 코드를 existsByKey로 바꾼 것과 같은 방향이에요.
🚌 해법 2 — 메시지 버스
그리고 받는 쪽입니다.
def handle(event: events.Event):
for handler in HANDLERS[type(event)]:
handler(event)
def send_out_of_stock_notification(event: events.OutOfStock):
email.send_mail("stock@made.com", f"Out of stock for {event.sku}")
정말 단순합니다. 딕셔너리 하나와 반복문 하나예요.
그런데 이 구조가 만드는 차이가 큽니다.
· 이메일을 SMS로 바꾸려면 → 핸들러만 고칩니다. Product는 안 건드려요
· 알림을 하나 더 추가하려면 → HANDLERS에 함수를 하나 더 넣습니다
· 재고 로직을 테스트하려면 → 메일 서버가 필요 없습니다. product.events에 뭐가 쌓였는지만 보면 돼요
테스트 코드가 그걸 보여줍니다.
def test_records_out_of_stock_event_if_cannot_allocate():
batch = Batch("batch1", "SMALL-FORK", 10, eta=today)
product = Product(sku="SMALL-FORK", batches=[batch])
product.allocate(OrderLine("order1", "SMALL-FORK", 10))
allocation = product.allocate(OrderLine("order2", "SMALL-FORK", 1))
assert product.events[-1] == events.OutOfStock(sku="SMALL-FORK")
assert allocation is None
메일이 안 나갑니다. 대신 "메일을 보내야 할 상황이라고 기록됐는지"를 확인해요.
🔀 CQRS — 읽기와 쓰기를 나누기
세 번째 패턴 정리도 명료합니다.
| 구분 | Command (쓰기) | Query (읽기) |
|---|---|---|
| 목적 | 상태 변경 | 데이터 조회 |
| 모델 | 도메인 모델 (복잡한 비즈니스 로직) | 읽기 전용 모델 (단순, 빠름) |
| 최적화 | 정합성, 트랜잭션 | 성능, 캐싱 |
요구가 정반대이기 때문입니다. 쓰기는 규칙을 지키는 게 중요해서 검증과 트랜잭션이 필요하고, 읽기는 빨리 보여주는 게 중요해서 캐싱과 비정규화가 필요해요. 한 모델이 둘 다 잘하기는 어렵습니다.
⚠️ 마이크로서비스 경고
그리고 이 글에서 가장 실무적인 경고가 있습니다.
"모노리스에서의 엉킨 코드보다 더 나쁜 상황이 벌어질 수 있다는 뜻이다."
나누는 것 자체가 답이 아니라는 지적입니다. 엉킨 코드를 그대로 두고 네트워크로 쪼개면, 엉킴은 그대로인데 디버깅만 어려워집니다. 함수 호출이 HTTP 요청이 되니까요.
그래서 이 글의 순서가 중요해집니다. 먼저 통신 방식을 설계하고, 그다음에 나누는 거예요.
큐레이터 노트
패턴을 문제에서부터 끌어낸 글이라 뽑았습니다.
아키텍처 패턴 글은 정의부터 시작하기 쉽습니다. "도메인 이벤트란 ~이다" 하는 식으로요. 그러면 언제 써야 하는지를 모른 채 이름만 외우게 됩니다.
이 글은 "이메일 로직을 어디에 넣지?"로 시작해요. 그리고 안티패턴 셋을 차례로 보여줍니다. 각각이 왜 안 되는지 이유가 다르고요. 그다음에 나오는 해법이라 필요성이 몸으로 이해됩니다.
"'그리고'가 나오면 SRP 위반"이라는 판별법도 값집니다. 원칙을 아는 것과 현장에서 적용하는 것은 다른데, 이런 검사법이 그 간격을 메워요.
분량이 33분인데도 구조가 안 흐트러진 것도 인상적이었습니다. Part 1과 Part 2를 잇는 흐름도를 그려두고, 각 패턴이 그 안 어디에 있는지 표시해요. 긴 글일수록 지도가 필요합니다.
8회차 AOP 글과 나란히 읽으면 특히 좋습니다. 둘 다 "본론 옆에 붙는 것들을 어디에 둘 것인가"를 다루거든요. AOP는 프록시로 가로채서 풀고, 이벤트 기반은 사실만 기록하고 넘겨서 풉니다. 같은 문제에 다른 해법이에요.
03
🍚 메뉴 취합하는 막내를 위해 Day1
신헌주 님
점심 메뉴 정하기를 실시간 웹앱으로 만든 기록입니다. React + Firebase로요.
동기가 아주 구체적이에요. "두 테이블 이상 되는 단체가 되면 'ㅇㅇ님은 뭐 드세요?'로 시작해서 카톡이 수십 개씩 쌓인다."
그리고 기존 투표 서비스로 안 되는 이유를 짚습니다. 후보가 미리 정해져 있다는 것. 여기서 이 프로젝트의 성격이 정해져요.
마지막에 헤맸던 부분 셋을 정직하게 적었습니다.
6회차 방통대 후기에 이어 두 번째로 소개하는 저자입니다. 제목의 "막내를 위해"가 이 프로젝트의 성격을 다 말해줘요.
조금 더 들어가 보면
문제 정의가 아주 생생합니다.
"후..." 한 글자에 상황이 다 담겨 있죠.
4회차 FOSS 리포트에 나왔던 "내가 불편한 것을 해결한 작은 도구"의 전형적인 사례입니다. 그리고 여기서는 내 불편이 아니라 막내의 불편이에요.
🎯 기존 서비스가 안 되는 이유
이 부분이 프로젝트의 방향을 결정합니다.
후보를 누가 정하느냐가 핵심 차이입니다.
일반 투표는 주최자가 선택지를 만들고 참가자는 고르기만 해요. 그런데 점심 메뉴는 그게 안 됩니다. 김치찌개, 돈까스, 파스타… 목록을 미리 다 적을 수가 없어요. 누군가는 아무도 생각 못 한 걸 먹고 싶어 하거든요.
그래서 결정권을 참가자에게 열어둔 설계가 됩니다. 앱은 선택지를 주지 않고, 모아서 보여주기만 해요.
이것도 같은 성격입니다. "이걸로 정합시다"라고 결론을 내주지 않고, 지금 상황을 그대로 비춰줘요. 그러면 사람들이 알아서 맞춰갑니다. 두 명이 같은 걸 고른 걸 보고 합류하거나, 다른 걸 골라 균형을 맞추거나요.
⚡ Firestore의 실시간 리스너
기술 선택 이유도 명확합니다.
onSnapshot이 그 기능입니다. 보통 서버 데이터를 화면에 보여주려면 내가 물어봐야 해요. 새로고침하거나, 몇 초마다 요청을 보내거나요.
실시간 리스너는 반대입니다. 데이터가 바뀌면 서버가 알려줍니다. 그러면 화면이 알아서 갱신돼요. 웹소켓을 직접 다루지 않고도 "누가 방금 투표했다"가 즉시 보이는 화면을 만들 수 있습니다.
검색 키워드를 단계마다 적어둔 것도 실용적이었어요.
"React랑 Firebase 세팅 등 세부적인 것은 검색하거나 AI에게 물으면 금방 나온다"고 밝히면서, 뭘 검색해야 하는지만 남겨뒀습니다. 요즘 튜토리얼의 실제 모습이에요. 막히는 건 방법이 아니라 검색어니까요.
🧱 헤맸던 부분 셋
이 글의 값이 여기 있습니다.
1. Firebase 초기 설정
코드는 다 맞는데 콘솔에서 안 만든 상황입니다. 그리고 그다음도 있어요.
Missing or insufficient permissions 에러가 났는데, 보안 규칙을 테스트 모드(allow read, write: if true)로 바꾸니 해결됐다."여기서 하나 덧붙여둘 게 있습니다. allow read, write: if true는 말 그대로 아무나 읽고 쓸 수 있다는 뜻이에요. 개발 중에는 편한데, 배포한 상태로 두면 누구나 데이터를 지울 수 있습니다. Firebase도 이 모드로 두면 30일 뒤 경고 메일을 보내요.
지금은 사내에서 쓰는 점심 투표라 큰 문제는 아니지만, 다음 단계로 챙길 만한 지점입니다. 글 제목이 "Day1"이니 이후 편에서 다뤄질 수도 있겠네요.
그리고 이 정리가 좋았습니다.
2. 이미지 삭제가 안 됨
가장 재미있는 버그입니다.
1707801234_photo.png), 삭제할 때는 원본 파일명(photo.png)으로 찾고 있었기 때문."저장할 때 이름을 바꿔놓고, 지울 때는 원래 이름으로 찾은 겁니다.
이게 아주 흔한 실수인 이유가 있어요. 저장 코드와 삭제 코드를 따로 쓰기 때문입니다. 저장할 때는 중복을 피하려고 타임스탬프를 붙이는데, 그 사실이 삭제 코드에는 안 전달돼요.
해결도 정석입니다.
storagePath 필드를 추가해서 정확한 경로를 저장하도록 수정해서 해결했다."규칙으로 재구성하는 대신 실제 경로를 저장했어요. 규칙으로 만들려 하면 나중에 명명 방식이 바뀔 때 또 깨집니다. 실제 값을 기록해두면 안 깨져요.
3. 그룹 기능 전환
votes, menuImages)으로 만들었다가, 그룹 기능을 위해 서브컬렉션(groups/{groupId}/votes) 구조로 전환했다."그리고 이 소감이 좋았습니다.
후회했다가 스스로 반박했어요.
그리고 이 반박이 맞습니다. 처음부터 그룹 기능을 넣었다면 훨씬 늦게 완성됐을 거예요. 그리고 그룹이 정말 필요한지도 모른 채 만들었을 겁니다. 1회차 오세명 님이 말한 "재사용을 미리 생각하는 게 오버엔지니어링으로 이어졌다"와 같은 자리입니다.
게다가 옮기는 비용도 크지 않았어요. "Firestore 경로만 바뀌는 거라 로직 자체는 거의 그대로인데, 경로를 하나하나 바꾸는 작업이 꽤 많았다." 귀찮았지만 어려운 일은 아니었던 겁니다.
🍽️ 기능 목록이 말해주는 것
만든 기능들이 실제 상황에 정확히 대응합니다.
| 기능 | 대응하는 상황 |
|---|---|
| 실시간 투표 | 스크롤 올려가며 정리하던 일 |
| 빠른 참여 (+ 버튼) | "저도 그거요" |
| 메뉴 사진 캐러셀 | "뭐 파는지 모르겠는데" |
| 가격 × 인원수 자동 계산 | 총액 계산 |
| 그룹 분리 | 두 팀이 동시에 |
| 전체 초기화 | 내일 또 |
하나하나가 원래 사람이 손으로 하던 일입니다. 그리고 하지 않은 것도 눈에 띄어요. 자동 추천도, 랜덤 뽑기도 없습니다. 결정은 여전히 사람이 합니다.
큐레이터 노트
작지만 끝까지 간 프로젝트라서 뽑았습니다.
점심 메뉴 투표는 규모로 보면 아주 작습니다. 그런데 문제 정의 → 기존 서비스 검토 → 기술 선택 → 구현 → 배포까지 다 있어요. 그리고 실제로 쓰이고 있습니다.
기존 서비스를 먼저 살펴본 것이 좋았습니다. "후보를 미리 정해야 한다"는 한계를 찾아냈고, 그게 직접 만들 이유가 됐어요. 3회차 김종진 님이 퀴즈 플랫폼을 비교할 때와 같은 방식입니다.
헤맨 부분을 따로 절로 만든 것이 이 글의 가장 큰 값입니다. 만든 결과보다 막힌 자리가 남에게 쓸모 있어요. Firestore를 콘솔에서 만들어야 한다는 것, 파일명이 바뀌면 삭제가 안 된다는 것 — 겪어본 사람만 알려줄 수 있는 것들입니다.
"일단 만들고 필요할 때 바꾸는 것도 나쁘지 않다"는 결론도 좋았어요. 자기 선택을 후회했다가 다시 저울질해서 받아들이는 과정이 그대로 보입니다.
제목의 "Day1"과 "막내를 위해"도 마음에 남았습니다. 계속 만들 생각이고, 누구를 위한 것인지가 제목에 있어요.
💡 세 편을 겹쳐 읽으며
역사 검색 앱 기획, 파이썬 아키텍처 정리, 점심 메뉴 투표. 크기도 영역도 완전히 다릅니다.
그런데 셋 다 같은 선택을 했어요. 결론을 자기가 내지 않고 넘겼습니다.
박석희 님의 앱은 "이렇게 이해하면 된다"고 말하지 않습니다. 관점 A와 B와 C를 나란히 놓고, 출처를 옆에 붙이고, "판단은 당신의 몫이다"로 끝나요. AI가 매끄러운 결론을 낼 수 있는데도 일부러 안 냅니다.
송영록 님이 정리한 Product는 "재고가 없다"는 사실만 적어둡니다. 그래서 이메일을 보낼지, SMS를 보낼지, 아무것도 안 할지 모릅니다. 그건 핸들러가 정해요.
신헌주 님의 앱은 메뉴를 추천하지 않습니다. 후보도 안 정해줘요. 지금 누가 뭘 골랐는지 비춰주기만 합니다.
셋 다 "내가 정할 수 있는데 안 정한" 겁니다. 그리고 그게 각각 이유가 있어요.
역사 앱은 정하면 반드시 누군가는 동의하지 않기 때문이고, Product는 알림 방식이 바뀔 때 재고 로직까지 흔들리면 안 되기 때문이고, 메뉴 앱은 먹고 싶은 걸 앱이 알 수 없기 때문입니다.
그런데 여기가 중요한데, 셋 다 그냥 방치한 게 아닙니다.
박석희 님은 판단할 재료를 잔뜩 갖춥니다. 출처 카드, 자료 성격(1차/2차), 시간 기준, 단계적 공개까지요. "앱은 결론을 내리지 않는다. 판단 가능한 상태를 만들어줄 뿐"이라고 정확히 적었어요.
OutOfStock 이벤트는 sku를 담고 있습니다. 어떤 상품이 떨어졌는지 모르면 핸들러가 아무것도 못 하니까요. 판단은 안 하되 판단에 필요한 건 넘깁니다.
메뉴 앱은 실시간 반영, 사진, 가격 × 인원수 자동 계산을 줍니다. 결정은 사람이 하되, 결정에 필요한 정보를 손에 쥐여줘요.
"정하지 않는다"와 "아무것도 안 한다"는 다릅니다. 셋 다 그 차이를 정확히 알고 설계했어요.
4기 열 편을 마치며
4기 서른 편을 열 회차로 나눠 읽었습니다. 다시 보니 회차마다 다른 축이 있었어요.
1회차는 "이 코드를 누가 읽는가"였고, 2회차는 "이 계산을 지금 여기서 해야 하는가"였습니다. 3회차는 "직접 할 일과 맡길 일의 선", 4회차는 "나는 무엇에 기대고 있는가", 5회차는 "의미를 기계가 읽는 형태로"였고요.
6회차는 "빠른 길과 남는 길", 7회차는 "참여자에서 기여자로", 8회차는 "본론이 아닌데 없으면 안 되는 것", 9회차는 "바뀌는 도중", 그리고 마지막이 "결론을 내리지 않는 설계"였습니다.
같은 사람이 여러 번 등장한 것도 4기의 특징이었어요. 오세명 님은 1·2·3회차에 연달아 나왔고 매번 결이 달랐습니다. 정민아 님은 SEO 작업(5회차)과 그 서비스의 한 해(8회차)와 그 서비스의 배포 장애(9회차)를 각각 남겼어요. 박석희 님은 교육을 논하고(6회차) 오픈소스를 정리하더니(7회차) 결국 만들기 시작했습니다(10회차).
한 편만 보면 글이지만, 여러 편을 이어 보면 사람이 보입니다. 성장일지를 계속 쓰는 이유가 거기 있는 것 같아요.
이번 회차에서 가져갈 것
1. RAG의 한계는 자료의 한계다.
모델을 아무리 좋은 걸 써도 없는 자료는 못 찾아옵니다. 파이프라인보다 어떤 자료를 모을 것인가가 먼저예요.
2. 차별점을 먼저 정의하고 시작하기.
"좋은 UX"는 답이 안 나오는데, "기존 서비스와 뭐가 달라야 하나"로 물으면 구체적인 원칙이 나옵니다.
3. AI가 쓴 문장은 매끄러워서 위험하다.
틀렸을 때도 매끄럽거든요. 근거 표시, 쟁점 뱃지, 확정적 표현 최소화 같은 장치로 일부러 확신을 덜어내야 합니다.
4. 답에는 시점을 붙이라.
"이 답변은 언제 기준 자료로 생성됨"을 표시하는 것만으로도 신뢰가 달라집니다.
5. "그리고"가 들어가면 SRP 위반이다.
"재고를 할당하고 그리고 실패하면 메일을 보낸다"면 두 가지 일이에요. 알림 방식이 바뀌었는데 재고 로직을 고치게 되면 잘못된 설계입니다.
6. 예외 대신 이벤트를 기록하라.
도메인 모델은 "무슨 일이 일어났는가"만 적어둡니다. 그래서 뭘 할지는 핸들러가 정해요. 테스트할 때 메일 서버가 필요 없어집니다.
7. 메시지 버스는 딕셔너리 하나다.
이벤트 타입 → 핸들러 목록 매핑과 반복문이면 끝이에요. 구조가 단순한데 얻는 게 큽니다.
8. 나누는 것 자체는 답이 아니다.
엉킨 코드를 그대로 두고 네트워크로 쪼개면 "분산된 거대한 진흙 덩어리"가 됩니다. 통신 설계가 먼저예요.
9. 선택지를 미리 정하지 않아도 되는 경우가 있다.
점심 메뉴처럼 후보를 다 적을 수 없는 영역에서는, 앱이 모아서 비춰주기만 하는 게 맞습니다.
10. Firebase는 콘솔 설정이 코드만큼 중요하다.
DB 생성과 보안 규칙을 안 하면 코드가 맞아도 아무 일이 안 일어납니다. 그리고 allow read, write: if true는 개발용이라는 걸 기억하세요.
11. 저장할 때 이름을 바꿨으면 그 경로를 기록하라.
규칙으로 재구성하려 하면 명명 방식이 바뀔 때 또 깨집니다. storagePath 같은 필드에 실제 값을 저장하세요.
12. 일단 만들고 필요할 때 바꾸는 것도 방법이다.
미리 확장성을 챙기면 그 기능이 정말 필요한지도 모른 채 만들게 됩니다. 나중에 옮기는 비용이 감당 가능하다면, 그게 나을 때가 많아요.
13. 정하지 않는 것과 아무것도 안 하는 것은 다르다.
결론을 안 내리더라도 판단에 필요한 재료는 갖춰서 넘겨야 합니다. 출처든, sku든, 총 예상 금액이든요.
좋은 글 남겨주신 박석희 님, 송영록 님, 신헌주 님께 감사드립니다! 😊
그리고 4기 열 회차 동안 글을 남겨주신 모든 분들께 감사드립니다. 다음은 5기 큐레이션으로 찾아뵙겠습니다! 🌱
- 에디터 · 성장일지 큐레이터 -
※ 본 큐레이션은 각 저자가 공개한 글을 소개하는 것이며, 모든 원문의 저작권은 저자에게 있습니다. 저자 본인의 요청이 있을 경우 즉시 수정 또는 삭제합니다.
'성장일지' 카테고리의 다른 글
| [GL 5기 1회차 ①] 약속 대신 구조가 지키게 — CQRS와 의존성 주입 · FSD 아키텍처 🌱 (0) | 2026.08.11 |
|---|---|
| [GL 5기 1회차] 약속 대신 구조가 지키게 — CQRS와 의존성 주입 · FSD 아키텍처 🌱 (0) | 2026.08.11 |
| [GL 4기 9회차 ②] 각자 자기 일만 하게 나누기 — MVVM 패턴, 예외 처리와 제네릭, 외적과 평면의 방정식 🌱 (0) | 2026.08.11 |
| [GL 4기 9회차 ①] 도중에 멈추면 생기는 일 — 배열 정렬 기법, 배포 직후 에러, Grafana 알림 🌱 (0) | 2026.08.11 |
| [GL 4기 8회차 ②] 미리 바꿔두면 나중이 싸집니다 — useMemo 메모이제이션, 파이썬 nonlocal과 클로저, 벡터의 성분 표현 🌱 (0) | 2026.08.11 |