Upgrade to Pro — share decks privately, control downloads, hide ads and more …

NestJSのDIコンテナで作るクリーンなレイヤー境界

 NestJSのDIコンテナで作るクリーンなレイヤー境界

NestJS Meetup Online #2 にて発表した内容です。

https://nest-jp.connpass.com/event/244015/

kimutyam

May 20, 2022
Tweet

More Decks by kimutyam

Other Decks in Technology

Transcript

  1. ࣗݾ঺հ w גࣜձࣾΧέϋγ d  w ҩྍ඼ൃ஫ɾ؅ཧ࠷దԽྖҬͷ৽نࣄۀͷ্ཱͪ͛Λ୲౰த w ιϑτ΢ΣΞΤϯδχΞσʔλΤϯδχΞΞʔΩςΫτ w

    Χέϋγͱ5ZQF4DSJQU w גࣜձࣾΧέϋγY5ZQF4DSJQU"EWFOU$BMFOEBS w ͳͥόοΫΤϯυ5ZQF4DSJQU͔ʁٕज़બఆഎܠͱ࣮ફྫΛ঺հ͠·͢ ໦ଜজ޺ ͖ΉΒ͖͋ͻΖ ˞ܦྺͷৄࡉ͸/FTU+4.FFUVQ0OMJOFͷΠϕϯτϖʔδʹهࡌ͍ͯ͠·͢
  2. w ΫϦʔϯΞʔΩςΫνϟͰ༗໊ͳਤͰ͢ɻ w ͜ΕΒͷಉ৺ԁͷ಺ଆʹ͍͘΄Ͳιϑτ΢ΣΞͱ ্ͯ͠ҐϞδϡʔϧʹͳΔ͜ͱ͕ࣔ͞Ε͍ͯ·͢ɻ w υϝΠϯۦಈઃܭͷจ຺ͩͱɺ&OUJUJFT͸ศ্ٓυ ϝΠϯϞσϧͱදݱ͢Δ৔߹͕͋Γ·͢ɻ w ಉ৺ԁͷ࿮ΛϨΠϠʔڥքͱݺͼ·͢ɻ

    w ಉ৺ԁͷ࿮Λԣஅ͢Δʮˠʯ͸ґଘؔ܎Ͱ͢ɻ ΫϦʔϯͳϨΠϠʔڥքΛ૊ΉͨΊͷߟ͑ํ ΫϦʔϯΞʔΩςΫνϟ IUUQTCMPHDMFBODPEFSDPNVODMFCPCUIFDMFBOBSDIJUFDUVSFIUNM
  3. ΫϦʔϯͳϨΠϠʔڥքΛ૊ΉͨΊͷߟ͑ํ ґଘؔ܎ٯసͷݪଇ %FQFOEFODZ*OWFSTJPO1SJODJQMF l্ҐϞδϡʔϧ͸͍͔ͳΔ΋ͷ΋ԼҐϞδϡʔϧ ͔Β࣋ͪࠐΜͰ͸ͳΒͳ͍ɻ૒ํͱ΋ந৅ʢྫͱ͠ ͯΠϯλʔϑΣʔεʣʹґଘ͢Δ΂͖Ͱ͋Δɻz˞ lந৅͸ৄࡉʹґଘͯ͠͸ͳΒͳ͍ɻৄࡉʢ۩৅త ͳ࣮૷಺༰ʣ͕ந৅ʹґଘ͢Δ΂͖Ͱ͋Δɻz˞ ґଘؔ܎ٯసύλʔϯͰ͸ɺΠϯλʔϑΣʔεΛ஥հ ͤ͞Δ͜ͱͰ্ҐϞδϡʔϧͷґଘΛճආ͍ͯ͠·

    ͢ɻ ,MPES౤ߘऀࣗ਎ʹΑΔஶ࡞෺ $$දࣔܧঝ IUUQTDPNNPOTXJLJNFEJBPSHXJOEFYQIQ DVSJEʹΑΔ ,MPES౤ߘऀࣗ਎ʹΑΔஶ࡞෺ $$දࣔܧঝ IUUQTDPNNPOTXJLJNFEJBPSHXJOEFYQIQ DVSJEʹΑΔ ӈ্ը૾ ӈԼը૾ ˞ IUUQTKBXJLJQFEJBPSHXJLJґଘੑٯసͷݪଇ ैདྷͷϨΠϠʔύλʔϯ ґଘؔ܎ٯసύλʔϯ
  4. w ґଘؔ܎ٯసͷݪଇʹैͬͨઃܭΛ͢Δͱɺ࠷ऴ తʹΠϯλʔϑΣʔεͷ࣮૷Λඥ෇͚Δඞཁ͕ग़ͯ ͖·͢ɻͦ͜Ͱɺґଘੑͷ஫ೖ %* Λߦ͍·͢ɻ w ্ͷίʔυ͸ɺίϯετϥΫλͰґଘੑΛ஫ೖ͢ ΔྫͰ͢ ίϯετϥΫλ%*

    ɻ w %*ͱ͸ɺґଘؔ܎ΛΫϥεؔ਺ͷ֎͔Β౉͢͜ͱ Ͱ͢ɻ w ͳ͓ɺԼͷίʔυ͸ɺ.FDIBOJTN಺෦Ͱ1PMJDZͷ ۩৅Ϋϥε͕ґଘͯ͠ɺґଘੑͷz஫ೖzʹ͸ͳͬͯ ͍·ͤΜɻ ΫϦʔϯͳϨΠϠʔڥքΛ૊ΉͨΊͷߟ͑ํ ґଘੑͷ஫ೖ %FQFOEFODZ*OKFDUJPO export class MechanismService { constructor(private policyService: IPolicyService) { } // ུ } export class MechanismService { private policyService: IPolicyService = new PolicyService( ) // ུ }
  5. w %*Λ࢖ͬͨΫϥεઃܭΛਐΊΔͱɺґଘؔ܎Λղ ܾ͢ΔӈͷΑ͏ͳίʔυ͕ੜ·Ε·͢ɻ w Ϋϥε͕গͳ͍͏ͪ͸໰୊ʹͳΓ·ͤΜ͕ɺ૿͑ ͖ͯͨ࣌͸ෆཁͳෳࡶੑΛੜΉ৔߹͕͋Γ·͢ɻ w %*ίϯςφ͸ӈͷίʔυΛॻ͔ͣʹґଘؔ܎ͷϏ δωεϩδοΫͷ֎ଆͰ؅ཧ͢Δ͜ͱ͕Ͱ͖· ͢ɻ

    w ͦͯ͠ɺ%*ίϯςφ͸/FTU+4ͷओཁͳػೳͷͭ Ͱ͢ɻ ΫϦʔϯͳϨΠϠʔڥքΛ૊ΉͨΊͷߟ͑ํ %*ίϯςφ͸ԿΛղܾ͢Δ͔ʁ new UtilityService ( new MechanismService ( new PolicyService( ) ) )
  6. w ·ͣ͸؆୯ͳྫ͔Βɻ w $BUT4FSWJDFͷґଘؔ܎͸ɺ!.PEVMFΛӈͷΑ͏ ʹఆٛ͢Ε͹%*Ͱ͖·͢ɻ w ͜ͷྫ͸ɺ$BUT4FSWJDFzΫϥεzΛ$BUT$POUSPMMFS ͰίϯετϥΫλ%*͍ͯ͠·͢ɻ /FTU+4ͷ%*ίϯςφͷ࢓૊Έ 1SPWJEFS

    @Controlle r class CatsService { } @Controller("cats") class CatsController { constructor(private catsService: CatsService) { } // ུ } @Module( { controllers: [CatsController] , providers: [CatsService] , } ) export class AppModule {}
  7. w ΠϯλʔϑΣʔεͷ৔߹͸ɺ޻෉͕ඞཁͰ͢ɻ w +BWB4DSJQU͸ΠϯλʔϑΣʔεΛαϙʔτͯ͠ ͍ͳ͍ͨΊɺ5ZQF4DSJQUΛ+BWB4DSJQUʹίϯ ύΠϧ͢ΔͱɺΠϯλʔϑΣʔεͷఆٛ͸ফࣦ ͠·͢ɻ w τʔΫϯ ྫͰ͸$"54@4&37*$&@50,&/

    Ͱ /FTU+4ͷ*OKFDUFSͰࣝผՄೳͳঢ়ଶʹ͢Δඞཁ͕ ͋Γ·͢ɻ /FTU+4ͷ%*ίϯςφͷ࢓૊Έ 1SPWJEFS interface ICatsService { } class CatsService implements ICatsService { } const CATS_SERVICE_TOKEN = "CATS_SERVICE_TOKEN" ; class CatsController { constructor ( @Inject(CATS_SERVICE_TOKEN ) private catsService: ICatsServic e ) { } // ུ } export const CatServiceProvider: Provider = { provide: CAT_SERVICE_TOKEN , useClass: CatsService , } ; @Module( { controllers: [CatsController] , providers: [CatServiceProvider] , } ) export class AppModule { }
  8. /FTU+4ͷ%*ίϯςφͷ࢓૊Έ !.PEVMFͱ͸Կ͔ʁ .PEVMF# .PEVMF" FYQPSUT JNQPSUT JNQPSUT 1SPWJEFS 1SPWJEFS $POUSPMMFS

    QSPWJEFST /FTU+4ͷΠϯδΣΫλʹΑͬͯΠϯελϯ εԽ͞Εɺগͳ͘ͱ΋Ϟδϡʔϧ಺Ͱڞ༗ ͞ΕΔ DPOUSPMMFST .PEVMF಺ͷΠϯελϯεԽ͢Δඞཁ͕͋Δ $POUSPMMFSҰཡ JNQPSUT ͜ͷϞδϡʔϧʹFYQPSU͢ΔผͷϞδϡʔ ϧͷҰཡ FYQPSUT ผͷϞδϡʔϧͰ͜ͷϞδϡʔϧΛJNQPSU ͢ΔͨΊͷɺެ։͢ΔϓϩόΠμٴͼτʔ ΫϯͷϦετ
  9. ϨΠϠʔڥքΛߏ੒͢Δ ߏ੒͢ΔΫϥεਤ w ֤ϨΠϠʔຖ EPNBJOVTF$BTF QSJNBSZ"EBQUPSTFDPOEBSZ"EBQUPS ʹͦΕͧ Ε.PEVMFΛ࡞੒͠·͢ w ͦΕΒΛ࠷ऴతʹ.PEVMFΛଋͶ·͢ɻ

    w <ิ଍>QSJNBSZͱTFDPOEBSZͱ͸ʁ w ΫϦʔϯΞʔΩςΫνϟͷલਐͱͳͬͨ 1PSUT"EBQUPSTΞʔΩςΫνϟͷ༻ޠͰ ͢ɻ w QSJNBSZ͸ʮۦಈ͢ΔʯΞμϓλ w TFDPOEBSZ͸ʮۦಈ͞ΕΔʯΞμϓλ
  10. w 3FQPTJUPSZ΍3FRVFTUFS͸*'ͷΈͰ͢ɻ w ۩ମతͳ࣮૷Λ͢ΔͱΞμϓλ૚ٴͼͦΕΑ Γ΋֎ͷԁʹґଘͯ͠͠·͏ͨΊͰ͢ɻ w Ұ൪্ҐϞδϡʔϧʹͳΔͨΊz஫ೖz͢Δґଘ͸ ͳ͍ͨΊɺ.PEVMF͸͋Γ·ͤΜɻ w ͳ͓ɺτʔΫϯ͸Ϣʔεέʔε૚ͷ࣮૷Ͱ࢖͏ͨ

    Ίɺ͜͜ʹ഑ஔ͍ͯ͠·͢ɻ ϨΠϠʔڥքΛߏ੒͢Δ υϝΠϯ૚ EPNBJO export type Cat = Readonly< { id: CatId ; name: CatName ; age: CatAge ; }> ; export const CAT_REPOSITORY_TOKEN = 'CAT_REPOSITORY_TOKEN' ; export interface ICatRepository { store(cat: Cat): Promise<void> ; } export const CAT_REQUESTER_TOKEN = 'CAT_REQUESTER_TOKEN' ; export interface ICatRequester { get(catId: CatId): Promise<Cat | undefined> ; }
  11. w ʮೣΛड͚औΔʯͱ͍͏ϢʔεέʔεΛ૝ఆͨ͠ ࣮૷Ͱ͢ɻ w 6TF$BTF͸*'Ͱɺͦͷ࣮૷Ϋϥε͸ *OUFSBDUPS Ͱ͢ɻ w *OUFSBDUPSͰ͸υϝΠϯ૚Ͱఆٛͨ͠*'Λίϯε τϥΫλͷҾ਺ʹఆ͍ٛͯ͠·͢ɻ

    ϨΠϠʔڥքΛߏ੒͢Δ Ϣʔεέʔε૚ 6TF$BTF export interface UseCase<In, Out> { run(input: In): Promise<Out> ; } export type Input = Readonly< { catId: CatId ; }> ; export type Output = Omit<Cat, 'id'> ; export type ReceiveCatUseCase = UseCase<Input, Output> ; export const RECEIVE_CAT_USECASE_TOKEN = 'RECEIVE_CAT_USECASE_TOKEN' ; @Injectable( ) export class ReceiveCatInteractor implements ReceiveCatUseCase { constructor ( @Inject(CAT_REPOSITORY_TOKEN ) private catRepository: ICatRepository , @Inject(CAT_REQUESTER_TOKEN ) private catRequester: ICatRequester , ) { } async run({ catId }: Input): Promise<Output> { const cat = await this.catRequester.get(catId); // ུ } }
  12. w ηΧϯμϦʔΞμϓλͷ.PEVMF ޙड़ ΛJNQPSU ͍ͯ͠·͢ w ϢʔεέʔεΛଞͷ.PEVMFͰར༻Ͱ͖ΔΑ͏ʹ ϓϩόΠμΛFYQPSU͍ͯ͠·͢ w ϓϥΠϚϦΞμϓλͷ.PEVMF

    ޙड़ ͰJNQPSU ͠·͢ɻ w <ิ଍>6TF$BTF.PEVMF͸ΠϯλʔϑΣʔεΞμ ϓλʹґଘ͍ͯ͠·͕͢ɺϩδοΫ͕ґଘ͍ͯ͠ ΔΘ͚Ͱ͸͋Γ·ͤΜɻ ϨΠϠʔڥքΛߏ੒͢Δ Ϣʔεέʔε૚ 6TF$BTF export const ReceiveCatUseCaseProvider: Provider = { provide: RECEIVE_CAT_USECASE_TOKEN , useClass: ReceiveCatInteractor , } ; @Module( { imports: [AdaptorMemoryStoreModule, AdaptorPetShopApiModule] , providers: [ReceiveCatUseCaseProvider] , exports: [ReceiveCatUseCaseProvider] , } ) export class UseCaseModule { }
  13. w υϝΠϯ૚ͷϦϙδτϦͷ*'Λ࣮૷͍ͯ͠·͢ɻ w ϦϙδτϦͷϓϩόΠμΛଞͷ.PEVMFͰ׆༻Ͱ ͖ΔΑ͏ʹFYQPSU͍ͯ͠·͢ɻ w Ϣʔεέʔεͷ.PEVMFͰJNQPSU͠·͢ɻ ϨΠϠʔڥքΛߏ੒͢Δ ΠϯλʔϑΣʔεΞμϓλ૚ 4FDPOEBSZ"EBQUPS

    @Injectable( ) export class CatRepository implements ICatRepository { store(cat: Cat): Promise<void> { // ུ } } export const CatRepositoryProvider: Provider = { provide: CAT_REPOSITORY_TOKEN , useClass: CatRepository , } ; @Module( { providers: [CatRepositoryProvider] , exports: [CatRepositoryProvider] , } ) export class AdaptorMemoryStoreModule { }
  14. w $BU$POUSPMMFSͷ࣮૷Ͱ͢ɻ w 6TF$BTF͸*'ͱ1SFTFOUFSͷΫϥεΛίϯετ ϥΫλͰఆ͍ٛͯ͠·͢ɻ ϨΠϠʔڥքΛߏ੒͢Δ ΠϯλʔϑΣʔεΞμϓλ૚ 1SJNBSZ"EBQUPS @Injector( )

    export class CatPresenter { } @Controller('/cat' ) export class CatController { constructor ( @Inject(RECEIVE_CAT_USECASE_TOKEN ) private receiveCatUseCase: ReceiveCatUseCase , private catPresenter: CatPresenter , ) { } @Put(':id' ) async receive(@Param('id') id: string): Promise<CatViewModel> { // ུ } }
  15. ϨΠϠʔڥքΛߏ੒͢Δ ΠϯλʔϑΣʔεΞμϓλ૚ 1SJNBSZ"EBQUPS w $POUSPMMFS ੨ ͷґଘؔ܎Λղܾ͢ΔͨΊʹɺ *OUFSBDUPSͷϓϩόΠμʔΛؚΉϢʔεέʔεͷ .PEVMF͕ඞཁʹͳΓ·͢ɻ w

    ·ͨɺ͜ͷྫͰ͸1SFTFOUFSͷґଘؔ܎΋ղܾ͢ Δඞཁ͕͋Γ·͢ɻ w ͜ΕΛ౿·͑ͯ࣍ͷϖʔδͰ͸.PEVMFΛ࡞੒͠ ·͢ɻ
  16. w Ϣʔεέʔεͷ.PEVMFΛJNQPSUͯ͠ɺ $POUSPMMFSͷίϯετϥΫλ%*͍ͯ͠·͢ɻ w DPOUSPMMFSTʹίϯτϩʔϥʔͷࢦఆΛ͍ͯ͠· ͢ɻ w 1SFTFOUFSΫϥεΛQSPWJEFSTʹࢦఆ͍͠·͢ɻ w <ิ଍>͜͜Ͱ͸Ϣʔεέʔεͷ*'ʹґଘ͍ͯ͠·

    ͕͢ɺϢʔεέʔεͷ*'Λ੾Βͣʹ۩৅Ϋϥεͷ ࢦఆͰ΋໰୊͋Γ·ͤΜɻ w *'ʹґଘ͍ͤͯ͞Δͷ͸$POUSPMMFSͷςελ ϏϦςΟΛ޲্ͤ͞ΔͨΊͰ͢ɻ ϨΠϠʔڥքΛߏ੒͢Δ ΠϯλʔϑΣʔεΞμϓλ૚ 1SJNBSZ"EBQUPS @Module( { imports: [UseCaseModule] , controllers: [CatController] , providers: [CatPresenter] , } ) export class AdaptorWebApiModule { }
  17. w ࠷ऴతʹ"QQ.PEVMFͰࠓ·Ͱ࡞੒ͨ͠.PEVMF ΛJNQPSU͠·͢ w ηΧϯμϦʔΞμϓλ͸6TF$BTF.PEVMFͰ JNQPSU͞Ε͍ͯΔͨΊ͜͜Ͱ͸ෆཁͰ͢ɻ w "QQ.PEVMFΛ/FTU'BDUPSZDSFBUFʹ౉ͯ͠׬ ੒Ͱ͢ɻ ϨΠϠʔڥքΛߏ੒͢Δ

    "QQ.PEVMF @Module( { imports: [UseCaseModule, AdaptorWebApiModule] , } ) export class AppModule { } export async function main() { const app = await NestFactory.create(AppModule) ; await app.listen(80, '0.0.0.0') ; }