Saya bukan peminat besar rangka kerja besar seperti NestJS; Saya sentiasa menyukai kebebasan membina perisian saya seperti yang saya mahu dengan struktur yang saya putuskan dengan cara yang ringan. Tetapi sesuatu yang saya suka semasa menguji NestJS ialah suntikan Ketergantungan.
Dependency Injection (DI) ialah corak reka bentuk yang membolehkan kami membangunkan kod yang digandingkan secara longgar dengan mengalih keluar tanggungjawab mencipta dan mengurus dependensi daripada kelas kami. Corak ini penting untuk menulis aplikasi yang boleh diselenggara, boleh diuji dan berskala. Dalam ekosistem TypeScript, TSyringe menonjol sebagai bekas suntikan pergantungan yang berkuasa dan ringan yang memudahkan proses ini.
TSyringe ialah bekas suntikan kebergantungan ringan untuk aplikasi TypeScript/JavaScript. Dikekalkan oleh Microsoft pada GitHub mereka (https://github.com/microsoft/tsyringe), ia menggunakan penghias untuk melakukan suntikan Pembina. Kemudian, ia menggunakan bekas Inversion of Control untuk menyimpan kebergantungan berdasarkan token yang boleh anda tukarkan untuk contoh atau nilai.
Sebelum menyelami TSyringe, mari kita terokai secara ringkas apa itu suntikan pergantungan dan mengapa ia penting.
Suntikan kebergantungan ialah teknik di mana objek menerima kebergantungan daripada sumber luaran dan bukannya mencipta kebergantungan itu sendiri. Pendekatan ini menawarkan beberapa faedah:
Mula-mula, mari sediakan TSyringe dalam projek TypeScript anda:
npm install tsyringe reflect-metadata
Dalam tsconfig.json anda, pastikan anda mempunyai pilihan berikut:
{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true } }
Import reflect-metadata pada titik masuk aplikasi anda:
import "reflect-metadata";
Titik masuk aplikasi anda ialah, sebagai contoh, reka letak akar pada Next.js 13+, atau ia boleh menjadi fail utama dalam aplikasi Express kecil.
Mari kita ambil contoh dari pengenalan dan tambahkan gula TSyringe:
Mari mulakan dengan penyesuai.
// @/adapters/userAdapter.ts import { injectable } from "tsyringe" @injectable() class UserAdapter { constructor(...) {...} async fetchByUUID(uuid) {...} }
Perhatikan penghias @injectable()? Ini untuk memberitahu TSyringe bahawa kelas ini boleh disuntik pada masa jalan.
Jadi Perkhidmatan saya menggunakan penyesuai yang baru kami buat. Mari masukkan Penyesuai itu ke dalam Perkhidmatan saya.
// @/core/user/user.service.ts import { injectable, inject } from "tsyringe" ... @injectable() class UserService { constructor(@inject('UserAdapter') private readonly userAdapter: UserAdapter) {} async fetchByUUID(uuid: string) { ... const { data, error } = await this.userAdapter.fetchByUUID(uuid); ... } }
Di sini saya juga menggunakan penghias @injectable kerana Perkhidmatan akan disuntik ke dalam kelas arahan saya, tetapi saya juga menambah penghias @inject dalam param pembina. Penghias ini memberitahu TSyringe untuk memberikan contoh atau nilai yang dimilikinya untuk token UserAdapter untuk sifat userAdapter pada masa jalan.
Dan akhir sekali, akar Teras saya: kelas arahan (sering salah dipanggil usecase).
// @/core/user/user.commands.ts import { inject } from "tsyringe" ... @injectable() class UserCommands { constructor(@inject('UserService') private readonly userService: UserService) {} async fetchByUUID(uuid) { ... const { data, error } = this.userService.fetchByUUID(uuid); ... } }
Pada ketika ini, kami telah memberitahu TSyringe perkara yang akan disuntik dan perkara yang perlu disuntik dalam pembina. Tetapi kami masih belum membuat bekas kami untuk menyimpan kebergantungan. Kita boleh melakukannya dalam dua cara:
Kami boleh mencipta fail dengan daftar suntikan kebergantungan kami:
// @/core/user/user.dependencies.ts import { container } from "tsyringe" ... container.register("UserService", {useClass: UserService}) // associate the UserService with the token "UserService" container.register("UserAdapter", {useClass: UserAdapter}) // associate the UserAdapter with the token "UserAdapter" export { container }
Tetapi kita juga boleh menggunakan penghias @registry.
// @/core/user/user.commands.ts import { inject, registry, injectable } from "tsyringe" ... @injectable() @registry([ { token: 'UserService', useClass: UserService }, { token: 'UserAdapter', useClass: UserAdapter }, ]) export class UserCommands { constructor(@inject('UserService') private readonly userService: UserService) {} async fetchByUUID(uuid) { ... const { data, error } = this.userService.fetchByUUID(uuid); ... } } container.register("UserCommands", { useClass: UserCommands}) export { container }
Kedua-dua kaedah mempunyai kebaikan dan keburukan, tetapi pada penghujung hari, ia adalah soal rasa.
Sekarang bekas kami dipenuhi dengan kebergantungan kami, kami boleh mendapatkannya daripada bekas seperti yang diperlukan dengan menggunakan kaedah penyelesaian bekas.
import { container, UserCommands } from "@/core/user/user.commands" ... const userCommands = container.resolve<UserCommands>("UserCommands") await userCommands.fetchByUUID(uuid) ...
Contoh ini agak mudah kerana setiap kelas hanya bergantung pada kelas lain, tetapi perkhidmatan kami mungkin bergantung pada banyak, dan suntikan pergantungan akan membantu memastikan semuanya kemas.
Tetapi tunggu! Jangan tinggalkan saya begitu! Bagaimana pula dengan ujian?
Suntikan kami juga boleh membantu kami menguji kod kami dengan menghantar objek olok-olok terus ke kebergantungan kami. Mari lihat contoh kod:
import { container, UserCommands } from "@/core/user/user.commands" describe("test ftw", () => { let userAdapterMock: UserAdapterMock let userCommands: UserCommands beforeEach(() => { userAdapterMock = new UserAdapter() container.registerInstance<UserAdapter>("UserAdapter", userAdapter) userCommands = container.resolve<UserCommands>("UserCommands") }); ... });
Kini token UserAdapter mengandungi olok-olok yang akan disuntik ke dalam kelas bergantung.
Gunakan token untuk menamakan: Daripada menggunakan literal rentetan untuk token suntikan, buat token tetap:
export const USER_REPOSITORY_TOKEN = Symbol("UserRepository");
Scoped containers: Use scoped containers for request-scoped dependencies in web applications.
Don't overuse DI: Not everything needs to be injected. Use DI for cross-cutting concerns and configurable dependencies.
If you've come this far, I want to say thank you for reading. I hope you found this article instructive. Remember to always consider the specific needs of your project when implementing dependency injection and architectural patterns.
Likes and comment feedback are the best ways to improve.
Happy coding!
Atas ialah kandungan terperinci TSyringe dan Suntikan Ketergantungan dalam TypeScript. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!