Coding Standards
HTML 요청부터 데이터베이스까지, 프로젝트의 전체 흐름과 계층별 개발 규칙을 확인합니다.
표준은 아키텍처마다 따로 둔다.
도메인 드리븐에서 정한 규칙이 3계층에서도 통한다는 보장이 없기 때문이다 —
Domain이 없는 구조에 「애그리거트 경계」를 들이밀 수는 없다.
아래 탭에서 아키텍처를 고르면 그 구조의 흐름과 표준만 보인다.
대상은 Spring Boot로 만드는 JSON API다. 서버가 HTML을 그리지 않기 때문에 화면 렌더링은 클라이언트가 맡고, 서버 쪽 표준은 진입점부터 시작한다. 각 탭의 표준은 서로 이어져 있고, 정한 순서대로 나열했다 — 뒤의 글은 앞의 글에서 정한 것을 전제로 삼는다.
깔려 있는 조건이 둘 더 있다. 여기서 코드를 주로 쓰는 것은 사람이 아니다 — Claude와 Codex와 Copilot이 같은 저장소에 파일을 직접 쓴다. 규칙을 도구 수만큼 배포하는 것도, 포맷을 도구로 강제하는 것도, 마이그레이션 파일명을 타임스탬프로 짓는 것도 여기서 갈렸다. 그리고 서비스는 하나고 인스턴스도 하나다. 조건이 다르면 그 자리에서 판단이 갈리므로, 갈리는 표준은 「먼저」 절에 그 조건과 조건이 뒤집혔을 때 어떻게 되는지를 적어 둔다. 갈리지 않는 표준에는 적지 않는다.
도메인 드리븐 아키텍처
최상위를 계층이 아니라 도메인으로 나누고, 도메인 하나가 api · application · domain · infra 네 겹을 갖는다
구조를 우리가 정할 수 있는 프로젝트에서 쓴다.
이 페이지는 사용자 요청이 UI, HTTP, Application, Domain, Repository 및 Database를 거쳐 처리되는 흐름과 각 계층에서 지켜야 하는 코딩 규칙을 설명합니다.
의존성은 외부 계층에서 내부 계층으로 향하며, Domain 계층은 UI, Database, Framework와 같은 외부 기술에 직접 의존하지 않습니다.
요청 한 사이클
사용자가 버튼을 누른 순간부터 데이터가 화면으로 돌아올 때까지, 요청이 어느 계층을 어떤 순서로 거치는지다. 계층 카드를 누르면 맨 아래 목록이 그 계층의 표준만 남긴다.
Request 사용자의 동작이 Database에 닿기까지
Client → api · web → application → domain → infra → Database순서로 흐른다. 계층을 고르면 아래 목록이 그 계층의 표준만 남긴다.
Response 조회한 데이터가 화면으로 돌아오기까지
- Database
- infra
- domain
- application
- apiweb
- Client
돌아오는 길에는 ResultSet, XxxEntity → XxxDomain, XxxDomain / XxxView, ServiceResult<XxxDomain>, HttpApiResponse<XxxResponse>
순서로 타입이 바뀐다. 각 계층은 자기 타입만 알고, 옆 계층의 타입은 모른다.
호출 방향과 의존 방향은 다르다. 요청은 위 그림대로 api에서 infra 쪽으로 흐르지만, 컴파일 의존은 infra가 domain을 향하는 반대 방향이다. application은 domain에 있는 Repository 인터페이스만 부르고, 그 구현이 infra에 있다는 사실을 모른다. 그 방향을 아래 계층 지도가 보여준다.
계층 지도
위 그림이 호출 순서라면 이 지도는 의존 방향이다. 최상위는 계층이 아니라 도메인으로 나누고, 도메인 하나가 아래 네 겹을 갖는다.
의존성은 바깥쪽에서 안쪽으로 향한다
어떤 계층도 아니다. 프레임워크에 의존하지 않는 것만 두기 때문에 모든 패키지가 참조해도 의존 방향이 깨지지 않는다.
Client와 Database는 요청 흐름에는 있지만 이 지도에는 겹으로 들어오지 않는다.
우리 코드가 아니기 때문이다. 이 둘과 맞닿는 규칙은 각각 api와 infra가 진다.
Domain은 UI, Database, Framework의 구체적인 구현을 알지 못한다.
바깥 겹이 안쪽 겹을 부르고, 안쪽은 바깥을 부르지 않는다.
infra가 바깥에 있는데도 domain을 향하는 이유가 이것이다 —
구현이 추상을 의존하지, 그 반대가 아니다.
계층별 표준
번호는 참조 순서다. 날짜순이 아니라, 앞 표준이 뒤 표준의 전제가 되는 순서로 놓았다.
-
팩토리 메소드로 VO 생성하기
DTO에서 VO로의 변환을 팩토리 메소드에 맡겨 Service의 변환 코드를 걷어낸다
자세히 보기 -
Null-free 객체 설계와 생성 철학
생성자가 객체의 완전성을 보장한다. 정적 팩토리, 불변성, 그리고 엔티티에서의 타협점
자세히 보기 -
Assert로 프로그래머의 실수를 잡아내기
내부 검증과 외부 검증의 경계를 나누고, 그 안쪽을 assert가 맡는다
자세히 보기 -
상속 vs 합성
DTO 공통 필드는 1레벨 상속으로 관리하고, 그 밖은 합성으로 간다
자세히 보기 -
애플리케이션 시간
사건 시각은 Service가 UTC Instant로 공급하고 날짜와 지역 시각은 의미에 맞는 타입으로 남긴다
자세히 보기 -
DB 시간 저장
사건 시점은 UTC 세션의 TIMESTAMP(6)로 왕복하고 행의 감사 시각은 DB가 만든다
자세히 보기 -
스키마 마이그레이션
스키마는 Flyway 한 벌이 소유하고 Hibernate와 테스트는 그걸 검증만 한다
자세히 보기 -
계층별 DTO 네이밍 표준
Controller, Service, JPA, MyBatis 사이의 의존성을 끊는 DTO 설계 원칙
자세히 보기 -
Swagger 문서화 표준
@Schema와 Bean Validation의 역할을 나누고, 공통 응답은 전역에서 관리한다
자세히 보기 -
계층별 검증 표준
무엇을 어디서 검증할지 가르는 기준은 "도메인 지식이 필요한가"이다
자세히 보기 -
경계 DTO의 null 표기
Java 호출 계약에는 orNull을 남기고 JSON 계약에는 키와 null을 함께 남긴다
자세히 보기 -
Service 계층의 assert
메서드의 전제와 결과를 코드로 선언하고, 깨지는 순간 멈추게 하는 방법
자세히 보기 -
DTO는 생성자로만 만든다
setter와 기본 생성자를 없애면 객체가 불완전한 상태로 존재할 수 없다
자세히 보기 -
예외 처리 표준
분류가 끝난 실패만 ErrorCodeException으로 운반하고 상태 코드, 메시지, 로그는 ErrorCode에서 통제한다
자세히 보기 -
성공 응답 상태
처리 결과가 200·201·202를 가르고 실제 상태와 본문 status, 코드 앞 세 자리를 맞춘다. 봉투가 면제되는 응답은 304 하나뿐이다
자세히 보기 -
예외의 종류
예외는 발생 경계에서 번역하고 ErrorCode는 식별 가능한 업무 도메인까지 세분화한다
자세히 보기 -
패키지 구조
도메인별로 나누고 package-private으로 막으면 계층 위반이 컴파일 에러가 된다
자세히 보기 -
컨트롤러 패키지 분리
JSON은 api, HTML은 web으로 가른다. 두 아키텍처가 들어오는 칸을 같은 이름으로 부른다
자세히 보기 -
접미사 패키지 분할
api·application은 나누면 질서가 생기고 infra는 나누면 깨진다. 나누는 김에 패키지 밖에서 안 쓰이는 것을 닫는다
자세히 보기 -
애그리거트 경계
Domain은 지연 로딩이 없다. 애그리거트 크기가 곧 매 조회의 비용이다
자세히 보기 -
Repository 설계 표준
저장과 단건 조회는 JPA로, 조건이 조합되는 검색은 MyBatis로, 대량 쓰기는 Repository 밖으로 뺀다
자세히 보기 -
UseCase 인터페이스
Service를 package-private으로 감추면 Controller가 쓸 수 있는 것이 컴파일러로 정해진다
자세히 보기 -
벌크와 배치
판단이 없으면 벌크, 있으면 배치. Domain을 거치는지는 결과이지 기준이 아니다
자세히 보기 -
Domain 영속화
POJO를 그대로 save하면 Domain이 모르는 컬럼이 조용히 null로 덮인다
자세히 보기 -
조회 전용 타입
들어가는 쪽에 SearchCondition이 있듯, 나가는 쪽에 View가 있다
자세히 보기 -
페이징 방식
타입이 곧 페이징 방식이다. 둘로 갈라 커서 응답에 totalCount를 실을 방법 자체를 없앤다
자세히 보기 -
삭제
지울 수 있는 애그리거트에만 delete를 둔다. 나머지는 컴파일러가 막는다
자세히 보기 -
인증 주체
LoginUser는 api에서 죽고, application으로는 userId만 간다
자세히 보기 -
목록 조회 요청 계약
들어오는 쪽 세 파라미터가 전부 인덱스 비용으로 번역된다. 그래서 허용 목록은 인덱스와 같은 층이 소유한다
자세히 보기 -
ErrorCode 계약
숫자에 도메인이 박혀 있어서 소유가 바뀌면 계약이 깨진다. 그래서 발급 자리를 먼저 정하고, 폐기는 지우지 않는 방식으로만 한다
자세히 보기 -
URL 라우팅 네이밍
리소스는 복수 명사로 짓고 도메인 이름과 버전은 경로에 넣지 않는다 — 예시 코드가 이미 그렇게 말하고 있었다
자세히 보기 -
아키텍처 소속
소속은 글이 들고 안 맞는 원칙에만 표시를 단다. 세어보니 142개 중 3개였고, 그 수가 글을 쪼개는 안을 지웠다
자세히 보기 -
API 테스트 계약표
안심이 안 되는 건 테스트가 적어서가 아니라 무엇을 안 봤는지 몰라서다. 계약을 표로 적으면 빈칸이 보이고, 색칠은 사람이 아니라 기계가 한다
자세히 보기 -
라우트-계약 정합성
계약 enum이 아무리 촘촘해도 컨트롤러가 실제로 받는 라우트와 다른 얘기를 하면 소용없다. RequestMappingHandlerMapping에서 직접 읽어 양방향으로 대조하고, 어긋나면 빌드를 막는다
자세히 보기 -
계약 커버리지 게이트
노랑은 두 종류였다 — 잊어서 안 짠 것과 지금 하네스로 못 짜는 것. 정적 스캔으로 다시 세고, 이유를 적은 유예만 빼주고, 나머지는 빌드를 막는다
자세히 보기 -
테스트 층위 선택
스텁은 외래키도 인증도 소프트 삭제도 통과시킨다. 기본을 실제 스택으로 두고 단위는 그 층에서 재현할 수 없는 것에만 쓴다
자세히 보기 -
테스트 냄새 어휘
「느리다」는 불평이고 「Too Many Tests 로 인한 Slow Tests」는 고칠 수 있는 것이다. 이름에 원인 목록이 딸려 온다
자세히 보기 -
코드 포맷터
포맷은 도구가 강제한다. 값은 취향으로 정하고 항목별 근거를 적지 않기로 정한 것 자체를 남긴다
자세히 보기 -
커밋 대기
커밋은 에이전트가 자기 일을 드러내야 하는 유일한 자리다. 거기서 안 멈추면 보고할 계기가 없다
자세히 보기 -
표준 동기화
어긋남은 세 축이다 — 도구 사이, 배포 시점 사이, 사본과 원본 사이
자세히 보기 -
배포 자동화
밀어내는 일만 자동으로 옮겨가고 그 앞을 지키던 검사는 남는다
자세히 보기 -
에이전트 운영
이슈를 넷으로 나눈 건 에이전트를 넷으로 나눈 부산물이었다. 문맥은 세션이 들고, 트래커에는 번호 붙은 계약과 사람용 댓글만 남긴다
자세히 보기 -
멀티 에이전트 위상
층이 셋인 이유는 역할이 셋이기 때문이다. 마스터는 아무것도 기억하지 않고 매번 orca에 묻는다
자세히 보기 -
일머리
정확도는 저울에 올리는 것이 아니라 전제다. 그 전제를 고정한 채 가장 빠른 길을 찾는다
자세히 보기 -
실패 알림
화면은 지켜지는데 터졌다는 사실이 아무 데도 안 간다. 알림은 통제되지 않은 곳이라 값을 싣지 않고 링크만 준다
자세히 보기
이 계층에는 아직 정한 표준이 없다. 비어 있는 자리는 아직 정하지 않은 것에 모아둔다.
3계층 아키텍처
최상위는 도메인으로 나누고, 도메인 하나가 요청 처리 · 비즈니스 로직 · 데이터 접근 세 겹을 갖는다
도메인 드리븐을 쓸 수 없는 자리. 외주처럼 구조를 우리가 정할 수 없는 일감에서는 네 겹으로 나누고 package-private으로 막는 방식을 강제할 수 없다.
3계층 아키텍처는 요청 처리, 비즈니스 로직, 데이터 접근의 책임을 각각 분리하는 구조입니다. 브라우저의 요청은 Controller와 Service를 거쳐 Repository 또는 MyBatis Mapper에 전달되고, 처리 결과는 반대 방향으로 반환됩니다.
요청 한 사이클
사용자가 버튼을 누른 순간부터 데이터가 화면으로 돌아올 때까지, 요청이 어느 계층을 어떤 순서로 거치는지다. 계층 카드를 누르면 맨 아래 목록이 그 계층의 표준만 남긴다.
Request 사용자의 동작이 Database에 닿기까지
Browser → api · web → service → repository → Database순서로 흐른다. 계층을 고르면 아래 목록이 그 계층의 표준만 남긴다.
Response 조회한 데이터가 화면으로 돌아오기까지
- Database
- repository
- service
- apiweb
- Browser
돌아오는 길에는 Query Result, Entity / Record, 결과 객체, HTTP Response
순서로 타입이 바뀐다. 각 계층은 자기 타입만 알고, 옆 계층의 타입은 모른다.
패키징은 도메인 드리븐과 같다 — 최상위를 도메인으로 나누고 계층은 그 안에 둔다(order/api, order/service, order/repository). 다른 것은 그 안이 몇 겹이냐다. Repository, DAO, MyBatis Mapper는 모두 데이터 접근 책임을 수행한다. 기술 스택에 따라 이름과 구현 방식은 달라질 수 있어서, 이 셋을 서로 다른 계층으로 나누지 않고 하나로 묶었다. domain이 없으므로 Repository 인터페이스로 의존을 뒤집지 않고, 호출 방향과 의존 방향이 같다. 첫 칸의 api와 web은 계층이 둘이라는 뜻이 아니다 — 같은 Presentation 계층의 패키지 둘이고, 나가는 것이 JSON이냐 HTML이냐만 다르다.
계층을 가리지 않는 규칙
이 구조의 계층별 책임, 요청 예시, 의존 규칙은 3계층 아키텍처 가이드에 따로 정리했다.
계층별 표준
번호는 참조 순서다. 날짜순이 아니라, 앞 표준이 뒤 표준의 전제가 되는 순서로 놓았다.
-
팩토리 메소드로 VO 생성하기
DTO에서 VO로의 변환을 팩토리 메소드에 맡겨 Service의 변환 코드를 걷어낸다
자세히 보기 -
Null-free 객체 설계와 생성 철학
생성자가 객체의 완전성을 보장한다. 정적 팩토리, 불변성, 그리고 엔티티에서의 타협점
자세히 보기 -
Assert로 프로그래머의 실수를 잡아내기
내부 검증과 외부 검증의 경계를 나누고, 그 안쪽을 assert가 맡는다
자세히 보기 -
상속 vs 합성
DTO 공통 필드는 1레벨 상속으로 관리하고, 그 밖은 합성으로 간다
자세히 보기 -
애플리케이션 시간
사건 시각은 Service가 UTC Instant로 공급하고 날짜와 지역 시각은 의미에 맞는 타입으로 남긴다
자세히 보기 -
DB 시간 저장
사건 시점은 UTC 세션의 TIMESTAMP(6)로 왕복하고 행의 감사 시각은 DB가 만든다
자세히 보기 -
스키마 마이그레이션
스키마는 Flyway 한 벌이 소유하고 Hibernate와 테스트는 그걸 검증만 한다
자세히 보기 -
Swagger 문서화 표준
@Schema와 Bean Validation의 역할을 나누고, 공통 응답은 전역에서 관리한다
자세히 보기 -
Service 계층의 assert
메서드의 전제와 결과를 코드로 선언하고, 깨지는 순간 멈추게 하는 방법
자세히 보기 -
DTO는 생성자로만 만든다
setter와 기본 생성자를 없애면 객체가 불완전한 상태로 존재할 수 없다
자세히 보기 -
예외 처리 표준
분류가 끝난 실패만 ErrorCodeException으로 운반하고 상태 코드, 메시지, 로그는 ErrorCode에서 통제한다
자세히 보기 -
성공 응답 상태
처리 결과가 200·201·202를 가르고 실제 상태와 본문 status, 코드 앞 세 자리를 맞춘다. 봉투가 면제되는 응답은 304 하나뿐이다
자세히 보기 -
예외의 종류
예외는 발생 경계에서 번역하고 ErrorCode는 식별 가능한 업무 도메인까지 세분화한다
자세히 보기 -
컨트롤러 패키지 분리
JSON은 api, HTML은 web으로 가른다. 두 아키텍처가 들어오는 칸을 같은 이름으로 부른다
자세히 보기 -
인증 주체
LoginUser는 api에서 죽고, application으로는 userId만 간다
자세히 보기 -
목록 조회 요청 계약
들어오는 쪽 세 파라미터가 전부 인덱스 비용으로 번역된다. 그래서 허용 목록은 인덱스와 같은 층이 소유한다
자세히 보기 -
ErrorCode 계약
숫자에 도메인이 박혀 있어서 소유가 바뀌면 계약이 깨진다. 그래서 발급 자리를 먼저 정하고, 폐기는 지우지 않는 방식으로만 한다
자세히 보기 -
URL 라우팅 네이밍
리소스는 복수 명사로 짓고 도메인 이름과 버전은 경로에 넣지 않는다 — 예시 코드가 이미 그렇게 말하고 있었다
자세히 보기 -
아키텍처 소속
소속은 글이 들고 안 맞는 원칙에만 표시를 단다. 세어보니 142개 중 3개였고, 그 수가 글을 쪼개는 안을 지웠다
자세히 보기 -
API 테스트 계약표
안심이 안 되는 건 테스트가 적어서가 아니라 무엇을 안 봤는지 몰라서다. 계약을 표로 적으면 빈칸이 보이고, 색칠은 사람이 아니라 기계가 한다
자세히 보기 -
라우트-계약 정합성
계약 enum이 아무리 촘촘해도 컨트롤러가 실제로 받는 라우트와 다른 얘기를 하면 소용없다. RequestMappingHandlerMapping에서 직접 읽어 양방향으로 대조하고, 어긋나면 빌드를 막는다
자세히 보기 -
계약 커버리지 게이트
노랑은 두 종류였다 — 잊어서 안 짠 것과 지금 하네스로 못 짜는 것. 정적 스캔으로 다시 세고, 이유를 적은 유예만 빼주고, 나머지는 빌드를 막는다
자세히 보기 -
테스트 층위 선택
스텁은 외래키도 인증도 소프트 삭제도 통과시킨다. 기본을 실제 스택으로 두고 단위는 그 층에서 재현할 수 없는 것에만 쓴다
자세히 보기 -
테스트 냄새 어휘
「느리다」는 불평이고 「Too Many Tests 로 인한 Slow Tests」는 고칠 수 있는 것이다. 이름에 원인 목록이 딸려 온다
자세히 보기 -
코드 포맷터
포맷은 도구가 강제한다. 값은 취향으로 정하고 항목별 근거를 적지 않기로 정한 것 자체를 남긴다
자세히 보기 -
커밋 대기
커밋은 에이전트가 자기 일을 드러내야 하는 유일한 자리다. 거기서 안 멈추면 보고할 계기가 없다
자세히 보기 -
표준 동기화
어긋남은 세 축이다 — 도구 사이, 배포 시점 사이, 사본과 원본 사이
자세히 보기 -
배포 자동화
밀어내는 일만 자동으로 옮겨가고 그 앞을 지키던 검사는 남는다
자세히 보기 -
에이전트 운영
이슈를 넷으로 나눈 건 에이전트를 넷으로 나눈 부산물이었다. 문맥은 세션이 들고, 트래커에는 번호 붙은 계약과 사람용 댓글만 남긴다
자세히 보기 -
멀티 에이전트 위상
층이 셋인 이유는 역할이 셋이기 때문이다. 마스터는 아무것도 기억하지 않고 매번 orca에 묻는다
자세히 보기 -
일머리
정확도는 저울에 올리는 것이 아니라 전제다. 그 전제를 고정한 채 가장 빠른 길을 찾는다
자세히 보기 -
실패 알림
화면은 지켜지는데 터졌다는 사실이 아무 데도 안 간다. 알림은 통제되지 않은 곳이라 값을 싣지 않고 링크만 준다
자세히 보기
이 계층에는 아직 정한 표준이 없다. 비어 있는 자리는 아직 정하지 않은 것에 모아둔다.
프론트엔드 아키텍처
앱이 둘이다. 보이는 값은 한 벌로 나누고, 만드는 것은 각자 만든다
Next.js 로 화면을 만드는 자리. 지금은 공개 사이트(client)와 관리자(admin) 둘이 돌고, 둘은 Docker 빌드 컨텍스트가 갈려 있어 서로의 파일을 못 읽는다.
계층이 없다. app · components · lib · styles 로 폴더는 나뉘어 있지만 그것이 계층인지, 무엇이 무엇을 부를 수 있는지, 컴포넌트와 레이아웃을 무엇으로 가르는지가 정해진 적이 없다. 실제로 Header 는 components/ 루트에 있고 Footer 는 components/sections/ 안에 있다 — 같은 레이아웃 조각인데 사는 곳이 다르다.
정하지 않은 것을 그려두면 다음에 읽을 때 정한 것으로 착각한다. 그래서 계층이 정해질 때까지 흐름도 자리는 비워둔다. 빈틈은 아직 정하지 않은 것에 올려뒀다.
여기서도 성립하는 표준
계층을 정하기 전에도 성립하는 것들이다. 두 앱이 무엇을 나눠 갖고 무엇을 따로 갖는지는 계층과 무관하게 먼저 정해야 했다.
-
프론트엔드 토큰 공유
겹치는 컴포넌트는 0개였고 겹치는 것은 토큰 121줄이었다. 보이는 값만 한 벌로 두고 컴포넌트는 각자 만든다
자세히 보기 -
컴포넌트 자리
폴더가 갈린 건 원인이 아니라 결과였다. 성격이 아니라 누가 그리는가로 가른다
자세히 보기 -
CSS 자리
같은 클래스가 다섯 파일에 다른 값으로 있고 마지막 로드가 이겼다. 이름 규칙 대신 도구가 막게 한다
자세히 보기 -
만들 것인가 가져다 쓸 것인가
50개 중 3개만 쓰고 직접 만들다 팝업의 focus trap 을 빠뜨렸다. 다시 만들어야 하는 것으로 가른다
자세히 보기 -
화면별 대응
같은 요금표를 표와 카드로 두 벌 그리고 있었다. 크기가 아니라 내용이 같은지를 먼저 묻는다
자세히 보기 -
태그 선택
div 202개에 ul 2개였다. div 는 붙일 이름이 없을 때 쓰는 마지막 칸이다
자세히 보기 -
컴포넌트 추출
빼는 이유는 둘뿐이다. 길이로 빼면 div 하나짜리 파일이 수십 개 생겨 읽기가 더 어려워진다
자세히 보기 -
프론트엔드 API 계층
새로 정할 것이 거의 없었다. 전송·도메인·화면 세 겹이 이미 서 있었고 말로 꺼내진 적만 없었다
자세히 보기 -
실패 화면
error.js 가 하나도 없어 렌더 중 버그가 터지면 화면이 사라진다. 예상한 실패와 예상 못 한 예외는 장치가 다르다
자세히 보기 -
폼 제출
쓰기 폼 일곱 개가 두 번 누르면 두 번 나간다. 방어를 글로 적으면 또 잊는다 — 훅으로 만들어야 지켜진다
자세히 보기 -
실패 알림
화면은 지켜지는데 터졌다는 사실이 아무 데도 안 간다. 알림은 통제되지 않은 곳이라 값을 싣지 않고 링크만 준다
자세히 보기 -
디자인 도구의 이름
공식 연결 경로가 플랜에 막혀 있다. 도구가 코드로 실어 보내는 여섯 자리에 코드에 있는 이름을 그대로 적는다
자세히 보기 -
Figma 리뷰 댓글 처리
「반영했다」와 「요청대로 됐다」는 다르다. 에이전트는 답글과 이력까지, 핀을 닫는 것은 사람이 한다
자세히 보기