ããã«ã¡ã¯ãæ ªå¼ä¼ç¤¾ããã¢ã¢ã®åæ¬ã§ã
ãã®è¨äºã¯ããã¢ã¢ã¢ããã³ãã«ã¬ã³ãã¼2024 2æ¥ç®ã®è¨äºã§ãã
æ¯ãè¿ãã¨æ¯å¹´TypeScriptã®åã«é¢ããè¨äºãæ¸ãã¦ãã¾ããã
https://engineering.meetsmore.com/entry/2022/12/06/215335
https://engineering.meetsmore.com/entry/2023/12/13/173512
ä»ã¾ã§ã®è¨äºã¯ç´¹ä»ç³»ã§ãããä»å¹´ã¯å®ç¨ç³»ã§è¡ããã¨æãã¾ããmongooseã®populateãselectãé©ç¨ããåãèªåçæããè¨äºã«ãªãã¾ããæå¾ã«åèªä½ãå ¬éãã¾ãã®ã§ãããã£ãã使ã£ã¦ãã£ã¦ãã ããã
ããã¢ã¢ã§ã¯mongodbã¨mongooseãçµã¿åããã¦å©ç¨ãã¦ããã®ã§ãããmongooseã®åã¯ãã¾ãå¼·ãããã¾ãããç¹ã«populateã¨ããRDBã ã¨joinã«è©²å½ããæ©è½ãå©ç¨ããéã¯å ¨ãåãè£å®ããã¦ããã¾ãããpopulateã¯mongooseç¬èªã®æ©è½ã§ãmongodbãã®ãã®ã¯ãªã¬ã¼ã·ã§ã³ã®æ¦å¿µãæã¡ã¾ããã
populateã®ç¹å¾´ã¨ãã¦ãåããã£ã¼ã«ããä¸æ¸ãããå½¢ã§joinãã¦ãã¾ãããã user: ObjectId | User
ã®ãããªåã«ããããå¾ã¾ããããããåã®è§£æ±ºãããããããããã¦ãã¾ãã
ããä»0ããmongodbã§ãããã¯ããæ§ç¯ããå ´åã¯Prismaãªã©ã®å¼·åãªåã®æ©æµãåãããã§ããã7年以ä¸éç¨ãã¦ãã¦ãããããã¯ããªãããã¼ãã«ã®æ°ãå¤ããmongooseã®æ©è½ã«ä¾åãã¦ããé¨åãããããç°¡åã«ç§»è¡ã§ãããããªç¶æ ã§ã¯ããã¾ããã
ãã ããã£ãããã«TypeScriptã§éçºãã¦ããã®ã ããåã®æ©æµã¯ååã«äº«åãããï¼ã¨ãããã¨ã§ä»ååããºã«ã«æããã¨ã«ãã¾ããã
ãã®åã説æããããã«ã¯ããã¤ãã®ãã¼ãã«ãå¿ è¦ã«ãªãã¾ãã®ã§ãCMSçãªãã®ãé¡æã«ãã¾ããã
ã¦ã¼ã¶ã¼ããã¦ãè¤æ°ã®ãã¹ãããããåãã¹ãã«ã¯ã³ã¡ã³ããè¤æ°æ稿å¯è½ã¨ãã£ãæãã«ãã¾ããããä»åã¯åããã¼ãã§ãã®ã§ããã®DBã¹ãã¼ããé©åãã©ããã¯èããªããã®ã¨ãã¾ãã
TypeScriptã§åå®ç¾©ãããã¨ä»¥ä¸ã®ãããªæ§é ã¨ãªãã¾ãã
type User = { _id: ObjectId name: string email: string // relations posts: ObjectId[] | Post[] } type Post = { _id: ObjectId title: string createdAt: Date // relations author: ObjectId | User comments: ObjectId[] | Comment[] } type Comment = { _id: ObjectId text: string // relations user: ObjectId | User post: ObjectId | Post }
ä»åã¯åã®ã話ãªã®ã§mongooseã®ã¹ãã¼ãå®ç¾©ã¯çç¥ãã¾ãããç´ ã®mongooseãå©ç¨ããã¨ä»¥ä¸ã®ãããªå¼ã³åºãããã¾ãã
const comment = await Comment.findOne({ _id: 1 }) .select(['text', 'post']) .populate({ path: 'user', select: ['name'] })
å¾ãããå®éã®ãã¼ã¿ã¯ä»¥ä¸ã®ãããªãªãã¸ã§ã¯ãã§ããâ¦
{ text: 'é¢ç½ãã£ãã§ã', post: ObjectId('67569d3d8d32177645326ed0'), user: { name: 'åæ¬' } }
åã¯ãããªã£ã¦ãã¾ã
{ _id: ObjectId; text: string; user: ObjectId | User; post: ObjectId | Post } // æ¬å½ã¯ãã®ããã«ãªã£ã¦æ¬²ãã { text: string; post: ObjectId; user: { name: string } }
ä»åã¯ãã®å解決ãå®ç¾ãã¦ããã¾ããmongooseã®ããã«ãã¤ãã§ç¹ãã§åãçæãã¦ããã®ã¯é¢åãããªã®ã§ãã¾ãã¯æ¸ãæ¹ãå¤ãã¦ãã¾ãã¾ãããã
Prismaãåèã«ããªãã¸ã§ã¯ãã§è«¸ã ãæå®ããå½¢ã«ãã¾ãã
â»ãããªæãã§ã§ãããè¯ããªã®ã¤ã¡ã¼ã¸
const commet = await Commenf.findOne({ _id: 1 }, { select: ['text', 'post'], populate: { user: { select: ['name'] }, } })
第äºå¼æ°ã«populateã¨selectãåæã«åãåãã¾ã
ããã®åå®ç¾©ã¯ä»¥ä¸ã®ããã«ãªã£ã¦ãã¾ãã
populateã¯PopulateOptionãå帰çã«å¼ã³åºããã¦ãã¦å ã ã®mongooseã¨åæ§ç¡éã«populateãç¹ããã¨ãå¯è½ã«ãªã£ã¦ãã¾ãã
ã¾ããé åãã©ãããåºå¥ããå¦çãå ¥ãã¦ãã¾ã
type PopulateOption<T, K extends keyof T> = { select?: T[K] extends Array<any> ? (keyof Exclude<T[K][number], ObjectId>)[] : (keyof Exclude<T[K], ObjectId>)[] populate?: T[K] extends Array<any> ? PopulateParam<Exclude<T[K][number], ObjectId>> : PopulateParam<Exclude<T[K], ObjectId>> } type PopulateParam<T> = { [K in PopulatableFields<T>]?: PopulateOption<T, K> }
ããã§å¼æ°ã®åã¯å®ç¾©ã§ãã¾ããããä»å欲ããã®ã¯è¿ãå¤ã®åã§ãã
GetResultå
ã¾ãã¯ä¸çªå¤å´ã®åã§ãã GetResult
åãå®ç¾©ãã¦ããã¾ãã
å¦çã¯3ã¤ã®è¦ç´ ã«å解ã§ãã¾ãã
å ã®å
{ _id: ObjectId text: string user: ObjectId | User post: ObjectId | Post }
- select ãã
{ text: string post: ObjectId | Post }
- ãã selectããå ´åã¯populateãããåãåé¤ãã
{ text: string post: ObjectId }
- populate & select ããï¼selectã¯1ãå帰çã«å©ç¨ãã¾ãï¼
{ text: string post: ObjectId user: { name: string } }
2 㨠1 + 3 ã®é¨åã«åãã¦ä»¥ä¸ã®ããã«ä½æãã¾ãã
type GetResult< T, P extends PopulateParam<T>, S extends keyof T = keyof T, > = RemovePopulatedType<Omit<Pick<T, S>, keyof P>> & _PopulatedDocument<T, P>
RemovePopulatedType
ObjectId | Post
ã ObjectId
ã«ããåãä½ãã¾ããpopulateå¯è½ãªãã£ã¼ã«ãã ãpopulateããã«selectããã ãã®é
ç®ã«å¯¾ãã¦å©ç¨ãã¾ãã
åã®ååéããObjectId以å¤ãæé¤ãããããã«å®è£ ããã¨è¤éã«ãªãããããpopulateå¯è½ãªé ç®ã§ããã°ObjectIdã«åºå®ãããå®è£ ã«ãã¦ãã¾ãã
mongooseã®ã¹ãã¼ãå®ç¾©ãå©ç¨ãã¦ããã°åºæ¬çã« _id: ObjectId
ã¨ããé
ç®ãããããããããç®å°ã«ãã¦ãpopulateå¯è½ããå¤å®ãã¦ãã¾ãï¼ PopulatableFields
ï¼
type RemovePopulatedType<T> = { [K2 in PopulatableFields<T>]-?: T[K2] extends Array<any> ? ObjectId[] : ObjectId } & { [K in Exclude<keyof T, PopulatableFields<T>>]-?: T[K] } /** * populateå¯è½ãã©ããã _id ãããããåºæºã«å¤å®ãã */ type PopulatableFields<T> = Exclude< { [K in keyof T]: T[K] extends { _id: ObjectId } | { _id: ObjectId }[] ? K : never }[keyof T], '_id' >
_PopulatedDocument<T, P>
ãããããã¡ã¤ã³ãã£ãã·ã¥ã§ããpopulateã¨ãã¦æå®ãããé ç®ãå帰çã«å¦çãã¦ããã¾ãã
ä»åã®ä¾ã§ãã¨æ·±ã1ã®populateããããã¾ããããmongooseã¯å帰çã«populateã®æå®ãå¯è½ã«ãªã£ã¦ãã¾ãã
ä¾ï¼
await Comment.findOne() .populate({ path: 'post', populate: { path: 'author', populate: { ... } } }) // { post: { author: { ... } } }
2ã¤ã®å _PopulatedDocument
㨠_PopulatedDocumentField
ãç¸äºã«å¼ã³åºãåã£ã¦åã解決ããå½¢ã«ãªã£ã¦ãããè£å©çãªåã¨ã㦠IsStrictlyEmptyObject
ãããã¾ãã
2ã¤ã®å½¢ã«åãã¦ããçç±ã¯é åãã©ããã®åå²ã¨selectããã¾ãã£ã¨populateã ãããéã®åå²ãåå©ç¨ãããã£ãããã§ãããããã1ã¤ã®åã§æ¸ããã¨ãå¯è½ã ã¨æãã¾ãï¼å¯èªæ§æ¿ä¸ããã ã¨ã¯æãã¾ãï¼
/** * populateãããããã¥ã¡ã³ãã®åãåå¾ãã * _PopulatedDocumentField ã¨ç¸äºã«å帰ãã¦ãã * ```ts * type Comment = { * _id: ObjectId * post: ObjectId | Post // ãããpopulate & selectããç¶æ ã«ããããã®å * } * ``` * */ type _PopulatedDocument<T, P extends PopulateParam<T>> = { [K in keyof P]-?: K extends keyof T ? // populate: { post: {} } ãªã©ã丸ã£ã¨ populate ããå ´å IsStrictlyEmptyObject<P[K]> extends true ? T[K] extends any[] ? RemovePopulatedType<Exclude<T[K][number], ObjectId>>[] : RemovePopulatedType<Exclude<T[K], ObjectId>> : T[K] extends any[] ? _PopulatedDocumentField<T[K][number], P[K]>[] : _PopulatedDocumentField<T[K], P[K]> : never } type IsStrictlyEmptyObject<T> = keyof T extends never ? {} extends T ? true : false : false /** * populateããããã£ã¼ã«ãã®åãåå¾ãã * _PopulatedDocumentã¨ç¸äºã«å帰ãã¦ãã * * ```ts * type Comment = { * _id: ObjectId * // ããã { title: string, author: { ..recurrsive } } ã«ããå * post: ObjectId | Post * } * ``` */ type _PopulatedDocumentField<T, P> = P extends { select: infer S populate?: infer SubPopulate } ? S extends (keyof Exclude<T, ObjectId>)[] ? // ObjectId | Post â Post ã¨ãã select æå®ã®ãã£ã¼ã«ãã«çµãè¾¼ã RemovePopulatedType< Pick<Exclude<T, ObjectId>, Exclude<S[number], keyof SubPopulate>> > & // ãã¹ãããã populate ãããå ´åã¯å帰çã«å¦çãã (SubPopulate extends PopulateParam<Exclude<T, ObjectId>> ? _PopulatedDocument< Pick< Exclude<T, ObjectId>, Extract< PopulatableFields<Exclude<T, ObjectId>>, keyof SubPopulate > >, SubPopulate > : {}) : never : P extends { populate: infer SubPopulate2 } ? Pick< Exclude<T, ObjectId>, Exclude<keyof Exclude<T, ObjectId>, keyof SubPopulate2> > & // ãã¹ãããã populate ãããå ´åã¯å帰çã«å¦çãã (SubPopulate2 extends PopulateParam<Exclude<T, ObjectId>> ? _PopulatedDocument< Pick< Exclude<T, ObjectId>, Extract< PopulatableFields<Exclude<T, ObjectId>>, keyof SubPopulate2 > >, SubPopulate2 > : {}) : T extends ObjectId[] ? ObjectId[] : ObjectId
åãå®è£ ã«å©ç¨ãã
ä½ã£ãå®è£ ãå©ç¨ãã¦ä»¥ä¸ã®ãããªé¢æ°ãä½ã£ã¦ãããã¨ããããã®ã¹ãã¼ãã«å¯¾ãã¦ãå©ç¨å¯è½ã§ãã
const findOneQuery = async < T, P extends PopulateParam<T>, S extends keyof T, >( model: Model<T>, cond: FilterQuery<T>, options: { select?: S[] populate: P } ): Promise<GetResult<T, P, S>> => { // æå®ããããªãã·ã§ã³ãå®éã®mongooseã«ããæãã«ãããã³ã°ãã¦ããã const query = _pipeQueries<T, P, S>(model.findOne(cond), options) const result = await query.exec() return result as GetResult<T, P, S> }
const comment = await findOneQuery( Comment, { _id: id }, { select: ['text', 'post'], populate: { user: { select: ['name'] }, } } ) //æ£ããåãåãã¾ããï¼ { text: string; post: ObjectId; user: { name: string } }
Future Work
ãã®å½¢ã«ã¯ä»¥ä¸ã®ãããªå¶éããããä»å¾è§£æ±ºãã¦ããããããå¼·ãmongooseã®åãçæã§ããã¨æã£ã¦ãã¾ã
ãã¹ãããããã£ã¼ã«ãã«å¯¾ããpopulateã«æªå¯¾å¿
mongodbã¯ããªãæè»ãªã¹ãã¼ããæã¤ãã¨ãã§ããããã以ä¸ã®ããã«æ¬æ¥å¥ãã¼ãã«ãããããªæ
å ±ã§ãç´æ¥æã¤ãã¨ãç°¡åã«ã§ãã¾ããããã«å¯¾ãã¦mongoose㯠'logs.user'
ãæå®ãã¦populateå¯è½ã§ãããä»åã®åã§ã¯å¯¾å¿ã§ãã¦ãã¾ããã
type Post = { logs: { action: 'edit' | 'create' | ... user: ObjectId | User }[] }
Virtualããã®ä»ã®ç´°ããªå¯¾å¿
mongooseã¯ORMã¨ãã¦virtualãã£ã¼ã«ããªã©å¤å½©ãªã«ã¹ã¿ãã¤ãºãå¯è½ã«ãªã£ã¦ããããã®è¾ºããæ£ç¢ºã«æ¾ãåããã¨ã¯ã§ãã¦ãã¾ããã
åã®ã¬ãã«ã§æ¾ãåãã®ã¯æ£ç´å³ããããããããã®æ©è½ãå©ç¨ããªããªã©ã®å¶éã課ãæ¹ãç¾å®çã§ã¯ãªããã¨æãã¦ãã¾ãã
å®ééç¨ãã¦ãã¦ããããªãã¨çãã¦ãããªããããªã¬ãã«ã®ãã®ã¯ãªããmongooseã¸ã®ä¾åãå¼·ãã¦å°æ¥çãªè² åµã«ãªãå¯è½æ§ã®æ¹ãé«ãã¨æãã¦ãã¾ãã
ã¾ã¨ã
mongooseã対å¿ãã¦ããªãpopulateãselectã«å¯¾ããåã®èªåçæã«ææ¦ãã90%ç¨åº¦ã®ã¦ã¼ã¹ã±ã¼ã¹ã«ã¯å¯¾å¿ã§ããåãä½ãã¾ããã
ä»ã¾ã§ as ...
ã§ãã£ã¹ããããªãã¦ã¯ãªããªãã£ãåãèªåã§çæããããã¨ã«ãããã°ã®å¯è½æ§ãæ¸ããéçºè
ä½é¨ãããããã¨ãã§ãã¾ããã
対å¿ã§ãã¦ããªãã¦ã¼ã¹ã±ã¼ã¹ã¯ä»å¾ãã¢ãããã¼ããã¦ããããä¸æ¹ã§ããã¾ãmongooseåºæã®æ©è½ã«ä¾åããªãå®è£ ã大åã§ãããã¨ãæãã¾ããã
æå¾ã«
ããã¢ã¢ã§ã¯æ§ã ãªè·ç¨®ã®ã¨ã³ã¸ãã¢ãç©æ¥µçã«æ¡ç¨ãã¦ãã¾ãï¼ ãèå³ãããæ¹ã¯ãã²æ°è»½ã«é¢è«ãã¾ãããï¼
- æ¡ç¨ãã¼ã¸: https://meetsmore.com/company/recruit
- åéè¦é : https://herp.careers/v1/meetsmore/3cQ_H41s6tZu
ãããã¯ãé¨ã§ã¯çµ¶è³äººæãåéãã¦ãããæ¬è¨äºã®ãããªTypeScriptã«å¼·ã人æãç¹ã«åéãã¦ãã¾ãï¼ä»ãã¸ã·ã§ã³ãå¤ãåéä¸ã¨ãªã£ã¦ããã¾ãã®ã§ãåæ¬å人å®ã§ãå ¬å¼ããã§ãã©ãã©ããå¿åãã ãã
åéä¸è¦§: https://hrmos.co/pages/meetsmore/jobs
- ã¨ã³ã¸ãã¢ãªã³ã°ããã¼ã¸ã£ã¼ï¼EM: https://hrmos.co/pages/meetsmore/jobs/Eng-005
- ã¨ã³ã¸ãã¢ãªã³ã°ããã¼ã¸ã£ã¼ï¼EMï¼é¨é·åè£ï¼: https://hrmos.co/pages/meetsmore/jobs/Eng-004
- ã·ãã¢Webã¢ããªã±ã¼ã·ã§ã³ã¨ã³ã¸ãã¢ï¼ããã¢ã¢ï¼ããã¯ã³ï¼: https://hrmos.co/pages/meetsmore/jobs/Eng-006
- ããã«Webã¢ããªã±ã¼ã·ã§ã³ã¨ã³ã¸ãã¢ï¼ããã¢ã¢ï¼ããã¯ã³ï¼: https://hrmos.co/pages/meetsmore/jobs/Eng-007
- ã¸ã¥ãã¢Webã¢ããªã±ã¼ã·ã§ã³ã¨ã³ã¸ãã¢ï¼ããã¢ã¢ï¼ããã¯ã³ï¼: https://hrmos.co/pages/meetsmore/jobs/Eng-008