사람함께에이전트▾
사람 — 직접 정하고 책임지는 비즈니스 규칙과 흐름. 직접 읽어보세요.
함께 — 개념은 알아두고, 세부 규칙은 에이전트가 따릅니다.
에이전트 — 에이전트가 따르는 규칙과 레퍼런스. 필요할 때 찾아보세요.
앱 & 라이브러리▾
도메인▾
스칼라▾
model.document.ts
model.document.ts는 저장된 모델을 어떻게 조회하고 바꿀지 정합니다. 데이터의 모양은 model.constant.ts가 정하고, 이 파일에는 service가 호출하는 재사용 쿼리, 상태 변경, 데이터베이스 헬퍼가 들어갑니다.service가 같은 쿼리를 반복할 때, 레코드의 상태가 바뀔 때, 테이블에 카운터나 로더, 인덱스가 필요할 때 이 파일을 엽니다.
이 페이지에서 쓰는 말
용어설명
filter
inProject처럼 이름 붙인 재사용 쿼리입니다. 하나마다 메서드 열네 개가 생깁니다.document
불러온 레코드 하나입니다.
set(), save()와 직접 만든 체인 메서드를 가진 인스턴스입니다.체인 메서드
this를 바꾸고 그대로 반환하는 document 메서드입니다. 이어 부른 뒤 save()는 한 번만 합니다.model
컬렉션 전체를 다루는 클래스입니다. service에서는
this.ticketModel로 접근합니다.this.Ticket
model 클래스 안에서 쓰는 테이블 파사드입니다.
pickById, find, updateOne 등을 제공합니다.훅
document를 쓰기 전이나 쓴 뒤에 실행되는 함수입니다.
쿼리 단위 쓰기
매치된 전부에 UPDATE를 한 번 실행합니다. 빠르지만 훅은 실행되지 않습니다.
표준 document 구조
데이터베이스 모듈의 document 파일은 클래스 세 개를 늘 이 순서로 선언합니다. 쿼리 하나와 체인 메서드 하나를 넣은 완성된 파일은 이렇습니다:
apps/koyo/lib/ticket/ticket.document.ts
클래스설명
TicketFilter
이름 붙인 쿼리와 정렬 순서입니다. 쿼리 하나가 model과 service에 메서드 열네 개로 생깁니다.
Ticket
불러온 레코드 하나입니다. 체인 메서드로 상태를 바꾸고 document 자신을 반환합니다.
TicketModel
컬렉션 전체를 다룹니다. 원자적 쓰기, 로더, 인덱스, 훅이 여기 있습니다.
- 순서는 고정입니다.
TicketFilter→Ticket→TicketModel순서이고, 비어 있어도sort: {}를 씁니다. - 이름은 constant를 따릅니다. 세 클래스 이름은
cnst.Ticket에서 오고,into()에는 소문자cnst.ticket을 넘깁니다. into()의 네 번째 인자는 로더 선언입니다. 로더가 없으면() => ({})를 씁니다.- 빈 모듈도 세 클래스를 모두 둡니다. 새 모듈은 본문이 빈 세 클래스로 시작하고, 이 클래스들이 코드가 들어갈 자리를 표시합니다.
쿼리, 정렬, 자동 생성 메서드
자주 쓰는 조건은 이름 붙인 쿼리로 한 번만 쓰고, service와 signal에서는 자동 생성된 메서드를 호출합니다.
inProject라는 쿼리는 listInProject, countInProject, existsInProject 외 열한 개의 메서드가 됩니다:apps/koyo/lib/ticket/ticket.document.ts
쿼리 만들기
빌더설명
filter()
이름 붙인 쿼리 하나를 시작합니다.
.arg(name, Type)
필수 인자입니다. 필수 인자는 모두 선택 인자보다 앞에 둡니다.
.opt(name, Type)
선택 인자입니다. 생략하면
undefined나 null이 들어오므로, 값이 있을 때만 조건을 더합니다..arg(name, ID, { ref })
id가 가리키는 모델을
{ ref: "user" }처럼 알려 줍니다. 그러면 admin 패널이 선택기를 보여 줍니다..query((...args, q) => …)
조건을 반환합니다.
q 헬퍼는 마지막 매개변수로 들어옵니다.sort: { key: { field: -1 } }
이름 붙인 정렬입니다.
{ sort: "highPriority" }처럼 키로 고릅니다. -1은 내림차순입니다.- 이미 들어 있는 것:
any쿼리(삭제되지 않은 전체)와latest,oldest,relevance정렬입니다. 비즈니스에 필요한 규칙만 더합니다. - 쿼리에
undefined를 넣지 않습니다.{ status: undefined }는 에러를 던지므로, 선택 인자가 없으면 키를 아예 뺍니다. - 정렬 키는 검사됩니다. filter에 없는 키를 넘기면 다른 정렬로 슬쩍 바뀌지 않고 거절됩니다.
q 헬퍼
대부분의 헬퍼는
{ status: q.oneOf(list) }처럼 필드 자리에 둡니다. 값이 있는지 보는 세 헬퍼만 필드 경로를 받습니다.헬퍼설명
q.allq.anyq.not
AND, OR, NOT으로 묶습니다.
all과 any는 false와 null을 건너뛰고, any 안의 {}는 모든 행과 매치합니다.q.eqq.ne
같음, 다름입니다.
{ status }처럼 값만 써도 같음입니다.q.oneOfq.notOneOf
목록 안에 있음, 없음입니다. 빈
oneOf는 아무것도, 빈 notOneOf는 모든 행과 매치합니다.q.gtq.gteq.ltq.lteq.between
숫자와 날짜의 범위 비교입니다.
q.has
배열 필드가 이 원소를 포함합니다. 배열 필드에 값만 써도 같은 뜻입니다.
q.contains
문자열 필드가 이 부분 문자열을 포함합니다.
q.empty(path)
필드에 값이 없습니다. 키가 없거나
null입니다. "값이 없음"은 이것으로 찾습니다.q.exists(path)q.missing(path)
키가 저장되어 있음, 없음입니다.
missing은 필드가 생기기 전에 쓴 행을 찾을 때 씁니다.q.when
조건이 참이면 쿼리를, 아니면
{}를 반환합니다.text 역할을 가진 필드에서 전문 검색을 합니다. 아래 텍스트 검색 쿼리 섹션을 보세요.q.raw(sql, params)
파라미터를 바인딩한 SQL 조각입니다. 쿼리가 특정 데이터베이스 방언에 묶입니다.
자동 생성되는 메서드 열네 개
쿼리 하나마다 메서드 열네 개가 model과 service에 똑같이 생깁니다. 그중 열 개는 읽기만 합니다:
list<Filter>Promise<Doc[]>
매치된 전부입니다. 옵션:
sort, skip, limit, select.listIds<Filter>Promise<string[]>
같은 결과를 id로만 읽습니다.
find<Filter>Promise<Doc | null>
매치 하나 또는
null입니다.findId<Filter>Promise<string | null>
같은 결과를 id로만 읽습니다.
pick<Filter>Promise<Doc>
매치 하나입니다. 없으면 에러를 던집니다.
pickId<Filter>Promise<string>
같은 결과를 id로만 읽습니다.
exists<Filter>Promise<string | null>
매치된 id 또는
null입니다. boolean이 아닙니다.count<Filter>Promise<number>
매치 개수입니다.
insight<Filter>Promise<Insight>
Insight 클래스가 선언한 카운터 전부입니다.
query<Filter>QueryOf<Doc>
쿼리를 실행하지 않고 동기로 만들기만 합니다. slice의
exec가 이것을 반환합니다.나머지 네 개는 쿼리 단위 쓰기입니다. 문장 하나로 데이터베이스에 바로 가며 훅을 실행하지 않습니다:
remove<Filter>Promise<UpdateResult>
원자적 UPDATE 한 번으로 매치 전부를 삭제 표시합니다.
removeOne<Filter>Promise<UpdateResult>
같은 동작을 가장 최근 매치 하나에만 합니다.
update<Filter>UpdateChain<Doc>
체인입니다. patch는 마지막
.set(patch)에 넘깁니다.updateOne<Filter>UpdateChain<Doc>
같은 동작을 가장 최근 매치 하나에만 합니다.
service에서는 이렇게 씁니다:
apps/koyo/lib/ticket/ticket.service.ts
count와insight는 같은 쿼리를 읽습니다.count는 숫자를,insight는db.<Model>Insight의 카운터 전부를 반환합니다.exists<Filter>는 boolean이 아닙니다. 매치된 id 또는null을 반환하므로=== true비교는 늘 실패합니다.removeOne과updateOne은 가장 최근 매치를 건드립니다. "이런 것은 많아야 하나"일 때 쓰는 것이지, 큐에서 다음 항목을 꺼내는 용도가 아닙니다.update<Filter>는 체인입니다. patch는 마지막.set()에 넘기고, 체인을 만드는 것만으로는 아무것도 바뀌지 않습니다.- 여기서는 projection을
select안에 넣습니다.listInProject(id, { select: { secret: true } })처럼 씁니다. 파사드의pickById(id, { secret: true })는 감싸지 않고 바로 받습니다.


쿼리 단위 쓰기 네 개는 훅을 실행하지 않습니다.
_postRemove도 캐스케이드도 없습니다. 삭제할 때 저장된 파일을 지우거나 자식을 닫는 model이라면, remove<Filter>는 그 일을 하나도 하지 않고 성공처럼 보이는 개수를 돌려줍니다. 삭제 부수효과가 없는 model에만 쓰고, 그렇지 않으면 service의 remove<Model>(id)로 하나씩 지웁니다.자동 생성 CRUD 메서드
쿼리 메서드 말고도, 모든 모델에는 아래 CRUD 메서드 여섯 개가 생깁니다.
get<Model>(id)Promise<Doc>
id 로더로 불러오고, document가 없으면 에러를 던집니다.
load<Model>(id?)Promise<Doc | null>
같지만, 에러 대신
null을 반환합니다. id가 비어 있어도 null입니다.load<Model>Many(ids)Promise<Doc[]>
여러 id를 쿼리 한 번에 묶어 불러옵니다.
create<Model>(data)Promise<Doc>
document 하나를 추가합니다.
save, create 훅이 실행됩니다.update<Model>(id, data)Promise<Doc>
document 하나를 고쳐 저장합니다.
save, update 훅이 실행됩니다.remove<Model>(id)Promise<Doc>
removedAt을 찍어 document 하나를 소프트 삭제합니다. remove 훅이 실행됩니다.service에서는 이렇게 부릅니다:
apps/koyo/lib/ticket/ticket.service.ts
- service에서 부릅니다. service 쪽 메서드는
_preCreate,_postRemove같은 service 훅도 실행하고,remove<Model>은 캐스케이드까지 실행합니다. model 쪽 메서드는 둘 다 건너뜁니다.
텍스트 검색 쿼리
q.search()는 field(String, { text: "title" })처럼 text 역할을 선언한 필드로 만든 전문 검색 인덱스를 조회합니다. 검색 전용 메서드는 따로 없습니다. bySearch라는 쿼리에서 listBySearch, countBySearch, queryBySearch, insightBySearch 등이 그대로 생깁니다.평범한 쿼리 노드라서 일반 조건과 함께 조합됩니다:
apps/koyo/lib/ticket/ticket.document.ts
검색 옵션
prefixboolean기본값 false
마지막 단어를 접두사로 취급합니다. 입력하면서 검색하는 입력창에 필요합니다.
columns("title" | "desc" | "tag" | "filter")[]
일부 컬럼으로 검색 범위를 좁힙니다. 예:
{ columns: ["title"] }. 생략하면 네 컬럼 모두 검색합니다.weightsnumber[]기본값 [10, 1, 3, 0]
title, desc, tag, filter 순서의 순위 가중치입니다. 음수가 아닌 유한한 숫자 네 개를 넘깁니다.
service에서는 다른 쿼리와 똑같이 부릅니다:
apps/koyo/lib/ticket/ticket.service.ts
규칙
- AND 위치에만 둡니다. 맨 바깥이나
q.all()안에 두고,q.any()나q.not()아래에는 두지 않습니다. - 빈 입력은 아무것도 매치하지 않습니다. 빈 검색창이 전체 목록으로 바뀌지 않게 하는 동작이므로, 전부 통과시키도록 "고치지" 않습니다.
- 가장 잘 맞는 순서로 보려면
relevance를 지정합니다. 다른 정렬 키를 주면 점수보다 그 키가 우선합니다. 정렬을 생략하면 service 호출은 점수순이지만, slice는latest를 씁니다. - 쿼리 단위 쓰기에는 검색을 쓸 수 없습니다. 검색 쿼리에서
update<Filter>,remove<Filter>와 그One버전을 부르면 에러가 납니다. - 모든 데이터베이스 모드에서 동작합니다. 같은 텍스트라면 SQLite와 Postgres가 같은 document를 찾고, Postgres에서는 순서만 다를 수 있습니다.
- service에서 검색하는 데는 filter로 충분합니다. slice를 달면 클라이언트에 공개되므로, 목록을 훑어도 괜찮은 모델에만 추가합니다.
검색어가 선택일 때
libs/shared의 admin 검색은 검색어를 선택 인자로 받고, 없으면 {}를 반환합니다. 그래서 검색창이 비면 admin 전체가 나옵니다. admin guard가 지키는 slice라서 괜찮은 동작이고, 공개 slice에서는 이렇게 하지 않습니다.libs/shared/lib/admin/admin.document.ts
document 하나 바꾸기
레코드 하나의 상태 변경은 document 클래스의 체인 메서드로 둡니다. 각 메서드는 검사하고,
this를 바꾸고, this를 반환합니다. 그래서 service는 여러 개를 이어 부르고 저장은 한 번만 합니다.apps/koyo/lib/ticket/ticket.document.ts
service는 불러오고, 이어 부르고, 저장합니다:
apps/koyo/lib/ticket/ticket.service.ts
- 검사하고, 바꾸고,
this를 반환합니다. 검증을 먼저 하고, 값을 바꾼 뒤,return this로 끝냅니다. - 메서드 안에서
save()하지 않습니다. 저장은 호출한 쪽이 한 번만 하므로org.removeUser(id).removeInvite(id).save()처럼 이어 붙일 수 있습니다. - 여러 필드를 한 번에 바꿀 때는
this.set({ … })를 씁니다.this를 반환하므로 그대로 메서드의 반환값이 됩니다. - 메서드마다 한 줄 주석으로 상태 전이를 적습니다. 예:
// draft -> opened



new Error가 아니라 new Err("ticket.error.<key>")를 던집니다. 그냥 Error를 던지면 lint가 실패해 빌드가 깨지므로, 키를 dictionary의 .error({})에 [en, ko]로 등록해 씁니다. 상태 전제 조건은 여기서 던지고, 여러 document에 걸친 규칙은 service에서 던집니다.model 헬퍼
컬렉션 전체를 다루는 일은 model 클래스에 둡니다. 원자적 업데이트, 일괄 쓰기, 카운터, 새 document 만들기가 여기 속합니다. 클래스 안에서는
this.Story가 테이블 파사드입니다:apps/koyo/lib/story/story.document.ts
- 카운터는 updater 콜백으로 씁니다.
({ inc }) => ({ viewCount: inc() })는 먼저 읽지 않고 원자적 UPDATE 한 번으로 실행됩니다. 결과는!!modifiedCount로 반환합니다. - updater 헬퍼:
set,unset,inc,mul,min,max,push,pull,addToSet,setOnInsert. 값만 쓰면set과 같습니다. - 삭제는 늘 소프트 삭제입니다. 모든 삭제는
removedAt을 찍을 뿐입니다. 모든 쿼리가 삭제된 행을 이미 걸러 내므로, filter에서removedAt을 따로 검사할 필요가 없습니다.
테이블 파사드
메서드설명
pickByIdpickOne
document 하나를 반환하고, 없으면 에러를 던집니다. 두 번째 인자는
{ secret: true } 같은 projection입니다.findByIdfindOnefind
에러 대신
null이나 목록을 반환합니다. find에는 .sort(), .skip(), .limit()을 이어 붙입니다.countexists
개수, 또는 매치된 id나
null입니다. countDocuments는 이제 쓰지 않는 옛 이름입니다.pickAndWritepickOneAndWrite
불러오고
set()하고 save()까지 한 번에 합니다. 그래서 save 훅이 실행됩니다.updateOneupdateManyremoveOneremoveMany
쿼리 단위 쓰기입니다. 문장 하나로 실행되고 훅은 없습니다.
One은 가장 최근 매치를 건드립니다.updateByIdremoveById
같은 훅 없는 쓰기를 id 하나로 좁힌 것입니다. document 경로가 아닙니다.
new this.Story(data)
저장되지 않은 document를 만듭니다.
save()하면 추가되고 save, create 훅이 실행됩니다.samplesampleOne
쿼리에 맞는 document를 무작위로 고릅니다.
bulkWrite
updateOne 여러 개를 한 번에 실행합니다. 각각 upsert를 켤 수 있습니다.라이브러리 모델 확장
앱은 라이브러리가 이미 정의한 모델에 기능을 더할 수 있습니다.
libs/shared의 user가 대표적입니다. 라이브러리 클래스를 마지막 인자로 넘기고, 앱이 더할 것만 씁니다.../__lib/lib.document가 모델별로 라이브러리 클래스를 모아 export합니다:apps/blog/lib/user/user.document.ts
- 클래스마다 spread 하나씩 넘깁니다.
...user.filters는from()에,...user.docs는by()에,...user.models는into()에 넣습니다. - 라이브러리의 동작이 합쳐집니다. 라이브러리의 쿼리, 정렬, document 메서드, model 메서드, 로더,
_onSchema훅이 모두 앱의 것과 합쳐집니다. - 수정할 때 spread를 지우지 않습니다. 하나라도 빠지면 그 클래스에서 라이브러리 메서드가 사라집니다.
lib/__lib/lib.document.ts는 자동 생성 파일입니다. 직접 고치지 않습니다. 앱이 의존하는 라이브러리에 맞춰 만들어집니다.
로더와 조회
로더는 같은 틱에 들어온 조회를 모아 쿼리 한 번으로 답합니다.
load()를 백 번 불러도 왕복은 한 번입니다. 자주 쓰는 조회 키가 있으면 into()의 네 번째 인자에 로더를 선언합니다.빌더설명
byField("sku")
필드 값 하나당 document 하나입니다. 그 키에 맞는 document나
null을 돌려줍니다.byArrayField("tags")
배열 필드에 그 키가 들어 있는 document 하나입니다.
byQuery(["shop", "orderNumber"] as const)
여러 필드 값의 조합 하나당 document 하나입니다.
단일 필드 로더는 키 하나로 찾습니다:
apps/koyo/lib/product/product.document.ts
여러 필드로 이루어진 키에는
byQuery를 씁니다:apps/koyo/lib/order/order.document.ts
- 없는 키는
null이 됩니다. 맞는 document가 없어도load()는 에러를 던지지 않습니다. - 모든 빌더는 두 번째 인자로 기본 쿼리를 받습니다. 예:
byField("sku", { status: "active" }) - id 로더는 기본으로 있습니다.
get<Model>,load<Model>,load<Model>Many가 이미 이 로더로 묶어 불러오며, batch가 끝나면 아무것도 기억하지 않습니다.


로더는 키 하나에 document 하나를 돌려줍니다. 목록이 아닙니다.
byField("seller")는 판매자마다 상품 하나만 돌려줍니다. "판매자의 모든 상품"은 로더가 아니라 listBySeller(sellerId) 같은 쿼리로 읽습니다.불러온 키 기억하기
모든 빌더는 세 번째 인자로 옵션 객체를 받습니다. 예:
byField("sku", {}, { cache: 60_000 }). 이 객체의 cache가 불러온 키를 메모리에서 얼마나 오래 답할지 정합니다.| cache | 불러온 키를 기억하는 기간 |
|---|---|
| false | 기본값입니다. 그 키를 불러온 batch가 끝나면 잊습니다. |
| 60_000 | 그 밀리초 동안 기억합니다. |
| true | 프로세스가 도는 동안 계속 기억합니다. |
- 기억해 둔 키는 오래된 document를 돌려줍니다. 로더는 프로세스와 수명이 같아서, 불러온 뒤에 바뀐 document도 키가 만료될 때까지 예전 모습으로 답합니다.
- 실패한 조회는 기억하지 않습니다. 그 키로 다시
load()하면 데이터베이스에 다시 묻습니다.
스키마 훅과 인덱스
static override _onSchema(schema)에는 테이블 자체에 필요한 것을 선언합니다. 인덱스와, 파생 필드를 맞춰 두는 작은 훅이 여기 들어갑니다. 비즈니스 흐름은 service에 둡니다.인덱스
자주 하는 조회는 인덱스로 빠르게 만듭니다:
apps/koyo/lib/story/story.document.ts
schema.index()의 두 번째 인자입니다:uniqueboolean기본값 false
이 필드들의 값이 같은 document를 두 번 넣지 못하게 합니다.
namestring기본값 <table>_<fields>_<position>
인덱스 이름을 고정합니다. 기본 이름은
_onSchema 안에서의 순서에 따라 달라집니다.- 검색은 인덱스가 아닙니다.
schema.index()는 일반 조회 인덱스만 만듭니다. 값"text"는 일반 인덱스의 옛 별칭일 뿐 검색이 아닙니다. 검색은 필드에text역할을 선언해서 켭니다. - 이름 붙인 정렬에는 인덱스가 저절로 생깁니다. filter의
sort에 있는 정렬마다 인덱스가 이미 있으므로, 자주 하는 조회에만 직접 선언합니다. - 빌더 형태도 있습니다.
schema.createIndex(name)에.path(field, order),.unique()를 이어 붙이고.done()으로 끝냅니다.


배포된 인덱스는 적힌 그대로 둡니다. 운영 중인 데이터베이스가 그 정의를 기억하므로,
"text"를 1로 바꾸거나 unique를 더하기만 해도 모두 깨집니다. 대신 새 인덱스를 맨 끝에 더하거나 name을 붙입니다.훅
훅은 파생 필드를 원본 필드에 맞춰 둡니다. 아래 훅은 story의 태그가 바뀔 때마다 태그 수를 다시 셉니다:
apps/koyo/lib/story/story.document.ts
어떤 쓰기에서 어떤 훅 이벤트가 실행되는지 정리했습니다:
이벤트
create
create<Model>
update
update<Model> · save()
remove
remove<Model>
쿼리 단위
update<Filter>
schema.pre("…", fn) · schema.post("…", fn)"save"
✓
✓
삭제를 뺀 모든 document 쓰기에서 실행됩니다.
"create"
✓
document를 새로 넣을 때만 실행됩니다.
"update"
✓
이미 있는 document를 저장할 때 실행됩니다.
"remove"
✓
remove<Model>(id)가 removedAt을 찍을 때 실행됩니다.✓실행됨실행되지 않음
- 훅은
async여도 되고next()를 부를 필요가 없습니다.this는 document이고, 세 번째 매개변수previous는 이번 쓰기 전의 행입니다. 생성할 때는 없습니다. this.isModified("field")는 save 훅 안에서 읽습니다.get<Model>이나list<Filter>로 막 읽어 온 document에서는 에러를 던집니다.- 생성할 때
isModified()는 늘false입니다. 생성에는previous가 없으므로, 예제처럼!previous를 먼저 확인합니다. post훅은 쓰기가 커밋된 뒤에 실행됩니다.pre훅은 그 전에 실행되므로 아직 document를 바꿀 수 있습니다.updatedAt은 자동으로 찍힙니다. 쿼리 단위 쓰기를 포함한 모든 쓰기에서 갱신되므로, 훅에서 직접 넣지 않습니다.
실전 규칙
코드 종류별로 세 클래스와 service 중 어디에 두는지 정리했습니다:
작성하는 코드
Filter
from()
Document
by()
Model
into()
Service
serve()
조회
재사용 조건
✓
그대로 두면 service 메서드마다 반복될 목록 조회나 단건 조회입니다.
정렬 순서
✓
highPriority 같은 이름 붙인 정렬입니다.자주 하는 조회
✓
자주 찾는 키에는 로더를, 자주 실행하는 쿼리에는 인덱스를 둡니다.
쓰기
상태 전이
✓
레코드 하나의 상태가 바뀝니다.
open(), approve() 같은 메서드입니다.상태 전제 조건
✓
레코드가 맞지 않는 상태면 체인 메서드가
Err를 던집니다.카운터 · 일괄 쓰기
✓
파사드로 UPDATE를 한 번 실행하고
!!modifiedCount를 반환합니다.파생 필드 · 인덱스
✓
_onSchema에서 하는 작은 저장 관련 작업입니다.조율
document 간 규칙
✓
관련된 document를 모두 불러온 뒤,
Err를 던지거나 저장합니다.쓰기의 부수효과
✓
_postCreate, _postRemove 같은 service 훅에 둡니다.✓여기에 둡니다여기가 아닙니다
자주 하는 실수
- 클래스 이름이 constant와 어긋나는 경우.
TicketFilter,Ticket,TicketModel은cnst.Ticket과 맞춥니다. - 같은 조건을 여러 service에 복사하는 경우. filter로 옮기고 어디서나
listInProject를 부릅니다. - 모델과 같은 이름의 filter.
Ticket에ticket쿼리를 두면removeTicket,updateTicket이 생기는데, 이 이름은 이미 CRUD 메서드의 것입니다. !!modifiedCount대신!!result를 반환하는 경우.updateOne은 객체를 반환하므로!!를 붙이면 늘true입니다.{ modifiedCount }를 먼저 꺼냅니다.- 스키마 훅에 무거운 흐름을 넣는 경우. 훅은 인덱스와 작은 파생 필드용이고, 흐름은 service에 둡니다.
- scalar document 파일이 커지는 경우. scalar의 document 파일은 보통
by(cnst.X)에 작은 헬퍼 한두 개면 충분합니다.
관련 페이지