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

運用していくアプリケーション開発のヒント

 運用していくアプリケーション開発のヒント

NestJS meetup Online #2 (https://nest-jp.connpass.com/event/244015/) で発表した資料です。

kenchan0130

May 20, 2022
Tweet

More Decks by kenchan0130

Other Decks in Technology

Transcript

  1. 1 運⽤していくアプリケーション開発のヒント 2022/05/20 NestJS Meetup Online #2 Copyright © 2019

    FOLIO Co., Ltd. All Rights Reserved.
  2. 2 ⾃⼰紹介 kenchan0130 Tadayuki Onishi Software Engineer @FOLIO 🔑 Tech

    keywords TypeScript Scala Ruby SRE AWS GCP Corporate IT https://kenchan0130.github.io 📄 Blog
  3. Agenda 3 1.アプリケーション開発・運⽤の現場では何が起 きるのか 2.クリーンアーキテクチャと正当性・堅牢性 3.クリーンアーキテクチャの実現 4.正当性・堅牢性の実現

  4. Agenda 4 1.アプリケーション開発・運⽤の現場では何が起 こるのか 2.クリーンアーキテクチャと正当性・堅牢性 3.クリーンアーキテクチャの実現 4.正当性・堅牢性の実現

  5. アプリケーション開発・運⽤の 現場では何が起こるのか 5

  6. 6 アプリケーション開発は変化の連続 実際にアプリケーションが使われてからじゃないと、 わからないことがたくさんある 変更・変化に対して、より速く対応して継続的にアプリケーション をデリバリーしていくことがプロダクトの競争⼒となる

  7. 質を下げて速度を上げる 7 デリバリー速度をあげるアンチパターン

  8. ʮ͋ͱͰΫϦʔϯʹ͢Ε͹͍͍Αɻઌʹࢢ৔ʹग़͞ͳ͚Ε͹ʂʯ ։ൃऀ͸ͦ͏΍͍ͬͯͭ΋͝·͔͢ɻ͕ͩɺ͋ͱͰΫϦʔϯʹ͢Δ͜ͱ͸ͳ ͍ɻࢢ৔͔ΒͷϓϨογϟʔ͸ࢭ·Βͳ͍͔Βͩɻʮઌʹࢢ৔ʹग़͞ͳ͚ Ε͹ʯͱ͍͏͜ͱ͸ɺޙΖʹڝ߹ଞ͕ࣾେ੎͍Δͱ͍͏͜ͱͰ͋Δɻڝ߹ ଞࣾʹ௥͍ൈ͔Εͳ͍ͨΊʹ͸ɺ͜Ε͔Β΋૸Γଓ͚Δ͔͠ͳ͍ɻ ͦͷ݁Ռɺ։ൃऀ͸ϞʔυΛ੾Γସ͑Δ͜ͱ͕Ͱ͖ͳ͍ɻ࣍ͷػೳɺ·ͨ ࣍ͷػೳɺ·ͨ·ͨ࣍ͷػೳΛ௥Ճ͢Δ͜ͱʹͳΓɺίʔυΛΫϦʔϯʹ ͢Δ͜ͱ·Ͱख͕ճΒͳ͍ɻ ͦͯ͠ɺ่յ͕࢝·Δɻੜ࢈ੑ͕θϩʹ͍͍ۙͮͯ͘ɻ ʰ$MFBO"SDIJUFDUVSFʱQ

    8 質を下げてもデリバリー速度は上がらない ࣭ͱεϐʔυʢ2022य़൛ɺ࣭ٙԠ౴༻ࢿྉ෇͖ʣ / Quality and Speed 2022 Spring Edition https://speakerdeck.com/twada/quality-and-speed-2022-spring-edition
  9. IUUQTPTBIBUFOBCMPHDPNFOUSZ Ͱ͸ɺ඼࣭ͱ଎౓ʹ͍ͭͯͷτϨʔυΦϑ͕ҙࣝ͞ΕΔ ͱ͖ɺ࣮ࡍʹ͸ԿͱԿ͕ṝʹ͔͚ΒΕ͍ͯΔͷ͔ɻ ͦΕ͸֤ݸਓͰ͸ͳ͘ϓϩμΫτશମͷ඼࣭ͱ଎౓͕ṝ ʹ͔͚ΒΕ͍ͯΔͷͰ͸ͳ͍͔ɻݴ͍׵͑Ε͹ɺϓϩμ Ϋτͷ඼࣭Λࢧ͑ΔͨΊʹඞཁͳϝϯόʔͷ੒௕ͱͦͷ ੒௕ͷͨΊʹඞཁͳϑΟʔυόοΫ΍ֶशͷ͕࣌ؒṝʹ ͔͚ΒΕ͍ͯΔͷͰ͸ͳ͍͔ͱࢥ͏ɻ ͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ w

    ʮ඼࣭ͱεϐʔυ͸τϨʔυΦϑͷؔ܎ʹ͋Δʯ͸େ͖ͳޡղ w ʮ඼࣭ʯͷ໊ͷ΋ͱʹ٘ਜ਼ʹ͞ΕΔͷ͸಺෦඼࣭ͷಛʹอकੑ ʢςετ༰қੑɺཧղ༰қੑɺมߋ༰қੑʣ w ࣮ࡍʹ͸อकੑΛߴΊΕ͹εϐʔυ͸্͕Δ͠ɺอकੑΛམͱͤ͹ εϐʔυ͸Լ͕Δ w εϐʔυΛམͱͯ͠΋อकੑ͸্͕Βͳ͍͠ɺεϐʔυΛམͱ͢ͱԾઆݕ ূϓϩηε͕ճΒͳ͍ w ಺෦඼࣭΁ͷ౤ࢿͷଛӹ෼ذ఺͸ҙ֎ͱૣ͘ ϲ݄Ҏ಺ ΍ͬͯ͘Δ w ϲ݄Ҏ಺ͱ͍͏͜ͱ͸डӹऀ͸ࣗ෼ͨͪࣗ਎Ͱ͋Γɺͭ·Γಓಙ΍ᛗዟ ͷ࿩Ͱ͸ͳ͘ଛಘͷ࿩Ͱ͋Δ w εϐʔυ͓Αͼ࣭ͱτϨʔυΦϑͳͷ͸ڭҭɺ੒௕ɺଟ༷ੑ΁ͷ౤ࢿ 9 質およびスピードは何とトレードオフなのか ࣭ͱεϐʔυʢ2022य़൛ɺ࣭ٙԠ౴༻ࢿྉ෇͖ʣ / Quality and Speed 2022 Spring Edition https://speakerdeck.com/twada/quality-and-speed-2022-spring-edition
  10. 仮説検証を繰り返して学びを得るしくみと、 組織的に技術⼒を⾼めるしくみを作ること 10 より良いプロダクトを作るには

  11. シンプル、つまり変更しやすい設計で備える 11 アプリケーション開発の⽂脈では

  12. 学んだ設計⼿法を使って、 ⽬の前にある複雑な課題に応⽤できないかを 常に考えて設計する 12 アプリケーション開発の⽂脈では

  13. Agenda 13 1.アプリケーション開発・運⽤の現場では何が起 こるのか 2.クリーンアーキテクチャと正当性・堅牢性 3.クリーンアーキテクチャの実現 4.正当性・堅牢性の実現

  14. クリーンアーキテクチャと 正当性・堅牢性 14

  15. ਖ਼౰ੑ ݎ࿚ੑ ৗʹਖ਼֬͞ɺਖ਼͠͞ΛॏΜ͡Δ ֎ք͔Βͷ༷ʑͳೖྗ΍ग़ྗΛɺίϯςΫετ ʹԠͯ͡ద੾ʹϋϯυϦϯά͠ɺม׵͢Δ • 円の外側(外界に近いレイヤー)ではより堅牢性、 円の内側(アプリケーションのコアロジックに近い レイヤー)ではより正当性を表現できる •

    関⼼事が分離できる • テストおよびリファクタリングがしやすくなる • 変更しやすいアプリケーションに近づく 15 - https://speakerdeck.com/twada/growing-reliable-code-phperkaigi-2022 クリーンアーキテクチャと堅牢性・正当性 ੺ͱ੨ͷք໘ɺ૚ͷ๷ޚϥΠϯ͕͋Δ
  16. ❌ クリーンアーキテクチャの図が意味するところは、 図の通りのレイヤーにすること ⭕ 常に内側にしか依存しないようにする、つまり依存 の⽅向を⼀⽅向にすること 16 クリーンアーキテクチャの図の意味

  17. Agenda 17 1.アプリケーション開発・運⽤の現場では何が起 こるのか 2.クリーンアーキテクチャと正当性・堅牢性 3.クリーンアーキテクチャの実現 4.正当性・堅牢性の実現

  18. クリーンアーキテクチャの実現 18

  19. 19 依存の⽅向を考えずに愚直に構成

  20. • Application層が、本来外側に位置する Infrastructure層に依存している • 依存性逆転の原則に違反している 20 依存の⽅向を考えずに愚直に構成

  21. 1. 上位のモジュールは下位のモジュールに依存しては ならない。どちらのモジュールも「抽象」に依存す べきである。 2. 「抽象」は実装の詳細に依存してはならない。実装 の詳細が「抽象」に依存すべきである。 21 依存性逆転の原則

  22. • Application層が外側のInfrastructure層に依存しな くなった • 業務ロジックが技術選択の関⼼事に依存しにくくな り、正当性が⾼いアプリケーションになる • 関⼼事が分離できた 22 抽象に依存するように変更

  23. NestJSのDIコンテナを使って実装してみる

  24. 抽象を定義 24 // application/repository/PostRepository.ts export type CreatePostProps = Readonly<{ postId:

    string; title: string; contents: Readonly<{ order: number; type: number; body: string; }>[]; }>; export abstract class PostRepository { abstract save(props: CreatePostProps): Promise<void>; }
  25. 抽象に関する詳細な実装 25 // infrastructure/repository/PostRepository.ts @Injectable() export class PostRepositoryImpl implements PostRepository

    { constructor(private readonly prismaClient: MyPrismaClient) {} async save(props: CreatePostProps): Promise<void> { await this.prismaClient.post.create({ data: { postId: props.postId, title: props.title, contents: { create: props.contents.map((v) => ({ contentOrder: v.order, contentType: v.type, body: v.body, })), }, }, }); } }
  26. DIで依存を逆転 26 // infrastructure/repository/Repository.module.ts const providers = [ { provide:

    PostRepository, useClass: PostRepositoryImpl, }, ]; @Module({ imports: [PrismaModule], providers, exports: providers.map((v) => v.provide), }) export class RepositoryModule {}
  27. DIで依存を逆転 27 // infrastructure/repository/Repository.module.ts const providers = [ { provide:

    PostRepository, useClass: PostRepositoryImpl, }, ]; @Module({ imports: [PrismaModule], providers, exports: providers.map((v) => v.provide), }) export class RepositoryModule {}
  28. UseCaseでは抽象を使⽤ 28 // application/usecase/CreatePost.usecase.ts @Injectable() export class CreatePostUseCase { constructor(private

    readonly postRepository: PostRepository) {} async execute( dto: CreatePostUseCaseInputDto ): Promise<CreatePostUseCaseOutputDto> { const postId = ulid(); await this.postRepository.save({ postId, title: dto.title, contents: dto.contents, }); return { postId }; } }
  29. UseCaseでは抽象を使⽤ 29 // application/usecase/CreatePost.usecase.ts @Injectable() export class CreatePostUseCase { constructor(private

    readonly postRepository: PostRepository) {} async execute( dto: CreatePostUseCaseInputDto ): Promise<CreatePostUseCaseOutputDto> { const postId = ulid(); await this.postRepository.save({ postId, title: dto.title, contents: dto.contents, }); return { postId }; } } UseCase
  30. UseCaseの依存をInfrastructure層で設定 30 // infrastructure/ioc/usecase/CreatePostUseCase.module.ts @Module({ imports: [RepositoryModule], providers: [CreatePostUseCase], exports:

    [CreatePostUseCase], }) export class CreatePostUseCaseModule {}
  31. 常に内側に依存するように、 依存の⽅向を⼀⽅向にできた

  32. そういえば、抽象をinterfaceじゃなくて abstract classで定義していたけど...

  33. interfaceはDIのトークンにできない 33 // application/repository/PostRepository.ts export interface PostRepository { createPost(props: CreatePostProps):

    Promise<void>; } // infrastructure/repository/Repository.module.ts @Module({ imports: [PrismaModule], providers: [ { provide: "PostRepositoryToken", useClass: PostRepositoryImpl, }, ], exports: ["PostRepositoryToken"], }) export class RepositoryModule {}
  34. interfaceはDIのトークンにできない 34 // application/repository/PostRepository.ts export interface PostRepository { createPost(props: CreatePostProps):

    Promise<void>; } // infrastructure/repository/Repository.module.ts @Module({ imports: [PrismaModule], providers: [ { provide: "PostRepositoryToken", useClass: PostRepositoryImpl, }, ], exports: ["PostRepositoryToken"], }) export class RepositoryModule {} interface interface
  35. 35 // application/usecase/CreatePost.usecase.ts @Injectable() export class CreatePostUseCase { constructor(@Inject("PostRepositoryToken") private

    readonly postRepository: PostRepository) {} 毎回挿⼊するのは⾯倒 Inject
  36. abstract classをDIのトークンにするTips 36 // application/repository/PostRepository.ts export abstract class PostRepository {

    abstract createPost(props: CreatePostProps): Promise<void>; } // infrastructure/repository/Repository.module.ts @Module({ imports: [PrismaModule], providers: [ { provide: PostRepository, useClass: PostRepositoryImpl, }, ] exports: PostRepository, }) export class RepositoryModule {}
  37. abstract classをDIのトークンにするTips 37 // application/repository/PostRepository.ts export abstract class PostRepository {

    abstract createPost(props: CreatePostProps): Promise<void>; } // infrastructure/repository/Repository.module.ts @Module({ imports: [PrismaModule], providers: [ { provide: PostRepository, useClass: PostRepositoryImpl, }, ] exports: PostRepository, }) export class RepositoryModule {} abstract class abstract class
  38. 38 Inject abstract classをDIのトークンにするTips // application/usecase/CreatePost.usecase.ts @Injectable() export class CreatePostUseCase

    { constructor(@Inject("PostRepositoryToken") private readonly postRepository: PostRepository) {}
  39. DI(Dependency Injection) を使うことで、関⼼事が分離できた

  40. DI(Dependency Injection) を使うことで、関⼼事が分離できたか?

  41. UseCaseで複数のテーブル操作を同⼀の DBトランザクションにしたいこともある

  42. 42 外側が内側に滲み出てしまう // application/usecase/CreatePost.usecase.ts @Injectable() export class CreatePostUseCase { constructor(

    private readonly postRepository: PostRepository, private readonly prismaClient: MyPrismaClient ) {} async execute( dto: CreatePostUseCaseInputDto ): Promise<CreatePostUseCaseOutputDto> { const postId = ulid(); await this.prismaClient.$transaction(async (prismaTransaction) => { await this.postRepository.createPost( { postId, title: dto.title, contents: dto.contents, }, prismaTransaction ); }); return { postId }; } }
  43. 43 外側が内側に滲み出てしまう @Injectable() export class CreatePostUseCase { constructor( private readonly

    postRepository: PostRepository, private readonly prismaClient: MyPrismaClient ) {} async execute( dto: CreatePostUseCaseInputDto ): Promise<CreatePostUseCaseOutputDto> { const postId = ulid(); await this.prismaClient.$transaction(async (prismaTransaction) => { await this.postRepository.createPost( { postId, title: dto.title, contents: dto.contents, }, prismaTransaction ); }); return { postId }; } } Infrastructure
  44. Async Hooksで 外側(Infrastructure)への依存を回避する

  45. • ⾮同期リソースをトラッキングできる • Node.js 8.2.1からExperimentalなAPIとして導⼊さ れた • 現時点(2022年5⽉20⽇現在)の最新版である 18.2.0においても、Experimentalである 45

    Async Hooksとは
  46. • スレッドローカルストレージのように使える • Async Hooksを使⽤して実現している 46 Continuation-Local Storage ( Hooked

    )
  47. 47 DBトランザクションも抽象に依存できる

  48. 実装してみる

  49. 49 clsを使ってContextを定義 // infrastructure/volatility/ClsContext.ts import * as cls from "cls-hooked";

    export class ClsContext<T> { private context: cls.Namespace; constructor(private readonly tokenKey: string) { this.context = cls.createNamespace(this.constructor.name); } async run<T>(fn: () => Promise<T>): Promise<T> { return this.context.runPromise(fn); } set(v: T): void { this.context.set(this.tokenKey, v); } get(): T | undefined { return this.context.get(this.tokenKey); } }
  50. 50 clsを使ってContextを定義 // infrastructure/volatility/PrismaTransactionContext.ts @Injectable() export class PrismaTransactionContext extends ClsContext<Prisma.TransactionClient>

    { constructor() { super("prismaTransactionClient"); } } // infrastructure/volatility/PrismaTransactionContext.module.ts @Module({ providers: [PrismaTransactionContext], exports: [PrismaTransactionContext], }) export class PrismaTransactionContextModule {}
  51. 51 トランザクションを表現する抽象を定義 // application/repository/DatabaseTransactionRepository.ts export abstract class DatabaseTransactionRepository { abstract

    runTransaction<T>(fn: () => Promise<T>): Promise<T>; }
  52. 52 抽象に関する詳細な実装 // infrastructure/repository/PrismaTransactionRepository.ts export class PrismaTransactionRepositoryImpl implements DatabaseTransactionRepository {

    constructor( private readonly client: MyPrismaClient, private readonly prismaTransactionContext: PrismaTransactionContext ) {} async runTransaction<T>(fn: () => Promise<T>): Promise<T> { return this.client.$transaction((transactionClient) => { return this.prismaTransactionContext.run(() => { this.prismaTransactionContext.set(transactionClient); return fn(); }); }); } }
  53. 53 依存を逆転させる // infrastructure/Repository.module.ts const providers = [ { provide:

    PostRepository, useClass: PostRepositoryImpl, }, { provide: DatabaseTransactionRepository, useClass: PrismaTransactionRepositoryImpl, }, ]; @Module({ imports: [ PrismaTestClientModule, PrismaTransactionContextModule ], providers, exports: providers.map((v) => v.provide), }) export class RepositoryModule {}
  54. 54 トランザクションのContextをDI // infrastructure/Repository.module.ts const providers = [ { provide:

    PostRepository, useClass: PostRepositoryImpl, }, { provide: DatabaseTransactionRepository, useClass: PrismaTransactionRepositoryImpl, }, ]; @Module({ imports: [ PrismaTestClientModule, PrismaTransactionContextModule ], providers, exports: providers.map((v) => v.provide), }) export class RepositoryModule {} Context
  55. 55 トランザクションのクライアントを使⽤ // infrastructure/repository/PostRepository.ts @Injectable() export class PostRepositoryImpl implements PostRepository

    { constructor( private readonly prismaClient: MyPrismaClient, private readonly prismaTransactionContext: PrismaTransactionContext ) {} async createPost(props: CreatePostProps): Promise<void> { const client = this.prismaTransactionContext.get() ?? this.prismaClient; await client.post.create({ data: { postId: props.postId, title: props.title, }, }); await Promise.all( props.contents.map((content) => client.postContent.create({ data: { postId: props.postId, contentOrder: content.order, contentType: content.type, body: content.body, }, }) ) ); } }
  56. 56 トランザクションのクライアントを使⽤ // infrastructure/repository/PostRepository.ts @Injectable() export class PostRepositoryImpl implements PostRepository

    { constructor( private readonly prismaClient: MyPrismaClient, private readonly prismaTransactionContext: PrismaTransactionContext ) {} async createPost(props: CreatePostProps): Promise<void> { const client = this.prismaTransactionContext.get() ?? this.prismaClient; await client.post.create({ data: { postId: props.postId, title: props.title, }, }); await Promise.all( props.contents.map((content) => client.postContent.create({ data: { postId: props.postId, contentOrder: content.order, contentType: content.type, body: content.body, }, }) ) ); } } Client
  57. 57 UseCaseでDBトランザクションを張る // application/repository/CreatePost.usecase.ts @Injectable() export class CreatePostUseCase { constructor(

    private readonly postRepository: PostRepository, private readonly databaseTransactionRepository: DatabaseTransactionRepository ) {} async execute( dto: CreatePostUseCaseInputDto ): Promise<CreatePostUseCaseOutputDto> { const postId = ulid(); await this.databaseTransactionRepository.runTransaction(async () => { await this.postRepository.createPost({ postId, title: dto.title, contents: dto.contents, }); }); return { postId }; } }
  58. 58 UseCaseでDBトランザクションを張る // application/repository/CreatePost.usecase.ts @Injectable() export class CreatePostUseCase { constructor(

    private readonly postRepository: PostRepository, private readonly databaseTransactionRepository: DatabaseTransactionRepository ) {} async execute( dto: CreatePostUseCaseInputDto ): Promise<CreatePostUseCaseOutputDto> { const postId = ulid(); await this.databaseTransactionRepository.runTransaction(async () => { await this.postRepository.createPost({ postId, title: dto.title, contents: dto.contents, }); }); return { postId }; } }
  59. Async Hooksを使うことで、 UseCaseで外側(Infrastructure)に依存せずに DBトランザクションを表現できた

  60. Agenda 60 1.アプリケーション開発・運⽤の現場では何が起 こるのか 2.クリーンアーキテクチャと正当性・堅牢性 3.クリーンアーキテクチャの実現 4.正当性・堅牢性の実現

  61. 正当性・堅牢性の実現 61

  62. ਖ਼౰ੑ ݎ࿚ੑ ৗʹਖ਼֬͞ɺਖ਼͠͞ΛॏΜ͡Δ ֎ք͔Βͷ༷ʑͳೖྗ΍ग़ྗΛɺίϯςΫετ ʹԠͯ͡ద੾ʹϋϯυϦϯά͠ɺม׵͢Δ • 正当性 • 常に正確さ、正しさを重んじる •

    堅牢性 • 外界から様々な⼊⼒や出⼒を、コンテクストに応 じて適切にハンドリングして変換する 62 [再掲]正当性・堅牢性 - https://speakerdeck.com/twada/growing-reliable-code-phperkaigi-2022
  63. 1. 堅牢性を担保したいがために、class-validatorを 使ったDtoで⾊々やりすぎてしまう • ドメイン知識と重複しがち 2. Validationで使うDtoは外界とのアダプターの役割 を持っている • つまり外側の持ち物なので、内部に持ち込めない

    • 持ち込むと正当性が脅かされる • 変更がしにくくなる 63 NestJSのValidationの問題点 (async () => { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); })(); export class CreateUserDto { @IsEmail() email: string; @IsNotEmpty() password: string; } @Controller() export class SampleController { @Post() create(@Body() createUserDto: CreateUserDto) { return 'This action adds a new user'; } } https://docs.nestjs.com/techniques/validation
  64. 使わない、または アダプターに関⼼があることだけやる 64 NestJSのValidationはどうすればいいのか

  65. じゃあどうすればいいのか

  66. あるモデル要素について、その属性しか関⼼の対象とな らないのであれば、その要素を値オブジェクトとして 分類すること。 値オブジェクトに、⾃分が伝える属性の意味を表現さ せ、関係した機能を与えること。 値オブジェクトを不変なものとして扱うこと。 同⼀性を与えず、エンティティを維持するために必要と なる複雑な設計を避けること。 66 DDDの値オブジェクトパターン

  67. 密接に関係し合うロジックが⼀箇所にギュッと集まっ ている構造を⾼凝集と呼びます。 ⼤事なのはデータとデータを操作するロジックを⼀箇 所にまとめて⾼凝集にすること、そして必要な操作だ けを外に公開するようカプセル化する考えです。 ⾼凝集な構造は、変更に強い、望ましい構造です。逆に 低凝集な構造は、壊れやすく変更が困難です。 67 ⾼凝集な構造

  68. ⽅法1)NewTypeで表現し、Prismで完全な値にする 68 ⾼凝集な構造で表現 import { prism } from "newtype-ts"; import

    type { Newtype } from "newtype-ts"; export type PostTitle = Newtype< { readonly POST_TITLE: unique symbol }, string >; export const PostTitle = { prism: prism<PostTitle>((s) => s !== ""), } as const; PostTitle.prism.getOption("タイトル") ⽅法2)オブジェクトで表現し、ファクトリで完全な値にする export class PostTitle { private constructor(private readonly value: string) {} asString(): string { return this.value; } // Either<NonEmptyArray<Error>, PostTitlte> を返すのもアリ static from(s: string): PostTitle { if (s !== "") { throw new Error("PostTitleに空文字は許可されません") } return new PostTitle(s) } } PostTitle.from("タイトル")
  69. 69 UseCaseに渡す前に値に変換する // presentation/posts/Posts.controller.ts @Controller("posts") export class PostsController { constructor(private

    readonly createPostUseCase: CreatePostUseCase) {} @Post() async createPost(@Body() dto: CreatePostDto): Promise<GetPostResponse> { const title = (() => { try { return PostTitle.from(dto.title) } catch (e) { throw new BadRequestException(e, "titleが不正な値でした"); } })(); return this.createPostUseCase.execute({ title }); } }
  70. アダプターの関⼼事と ドメインの関⼼事を分離できた ⾼凝集な値でロジックが散らばりにくくなった

  71. 71 まとめ • NestJSのDIにより依存性逆転の原則を適⽤ • 業務ロジックが技術選択の関⼼事に依存しにくくなり、正当性が⾼いアプリケーションになる • 正当性を重んじるレイヤーと堅牢性を重んじるレイヤーを分けることで関⼼事が分離できる • ⾼凝集な表現により、制約や意図を型として表現可能になり、堅牢なコードが書ける

  72. 採⽤情報について 詳細はこちらから フロントエンドエンジニアほか ポジション採⽤を⾏っています

  73. THANK YOU

  74. 74 [Appendix] 正当性と堅牢性 - https://speakerdeck.com/twada/growing-reliable-code-phperkaigi-2022 ਖ਼౰ੑͱݎ࿚ੑ w ࠷దͳΤϥʔॲཧ͸Τϥʔ͕ൃੜͨ͠ιϑτ΢ΣΞͷछྨʹΑΓҟͳΔ w ਖ਼౰ੑͱ͸ɺෆਖ਼֬ͳ݁ՌΛܾͯ͠ฦ͞ͳ͍͜ͱΛҙຯ͢Δɻෆਖ਼֬ͳ

    ݁ՌΛฦ͘͢Β͍ͳΒɺԿ΋ฦ͞ͳ͍ํ͕·͠Ͱ͋Δ w ݎ࿚ੑͱ͸ɺιϑτ΢ΣΞͷ࣮ߦΛܧଓͰ͖ΔΑ͏ʹखΛਚ͘͢͜ͱͰ ͋ΔɻͦΕʹΑͬͯෆਖ਼֬ͳ݁Ռ͕΋ͨΒ͞ΕΔ͜ͱ͕͋ͬͯ΋͔·Θ ͳ͍ w ҆શੑ ΍ਖ਼֬ੑ Λॏࢹ͢ΔΞϓϦέʔγϣϯͰ͸ɺݎ࿚ੑΑΓ΋ਖ਼౰ ੑ͕༏ઌ͞ΕΔ܏޲ʹ͋Δ w ίϯγϡʔϚΞϓϦέʔγϣϯͰ͸ɺਖ਼౰ੑΑΓ΋ݎ࿚ੑ͕༏ઌ͞ΕΔ ܏޲ʹ͋Δ IUUQTXXXBNB[PODPKQEQ9
  75. • Keep it simple, stupid. • ケリー・ジョンソン⽒によって提唱された原則 • 不必要な複雑性は避けるべきである、設計の単純 性・簡潔性は成功への鍵

    • アプリケーションの変更は複雑性が⾼くなり、保守 コスト・拡張コストが⾼くなる • シンプルなコードを保つことが好ましい 75 [Appendix] KISSの原則 https://en.wikipedia.org/wiki/ Kelly_Johnson_(engineer)