diff --git a/DependencyInjection/src/Step1/Database.ts b/DependencyInjection/src/Step1/Database.ts new file mode 100644 index 0000000..ad87ae6 --- /dev/null +++ b/DependencyInjection/src/Step1/Database.ts @@ -0,0 +1,9 @@ +export class Database { + public connect() { + return '[Database] connected to database'; + } + + public query(sql) { + return `Executed [${sql}]`; + } +} diff --git a/DependencyInjection/src/Step1/Logger.ts b/DependencyInjection/src/Step1/Logger.ts new file mode 100644 index 0000000..6ef3d50 --- /dev/null +++ b/DependencyInjection/src/Step1/Logger.ts @@ -0,0 +1,23 @@ +export class Logger { + private _category: string; + + constructor(category: string) { + this._category = category; + } + + public info(message: string, ...optionalArgs: unknown[]): void { + const formattedMessage = this.formatMessage('INFO', message); + + console.log(formattedMessage, ...optionalArgs); + } + + public error(message: string, ...optionalArgs: unknown[]): void { + const formattedMessage = this.formatMessage('ERROR', message); + + console.error(formattedMessage, ...optionalArgs); + } + + private formatMessage(level: string, message: string): string { + return `${new Date().toISOString()} [${this._category}] [${level}] ${message}`; + } +} diff --git a/DependencyInjection/src/Step1/SimpleContainer.ts b/DependencyInjection/src/Step1/SimpleContainer.ts new file mode 100644 index 0000000..cb42506 --- /dev/null +++ b/DependencyInjection/src/Step1/SimpleContainer.ts @@ -0,0 +1,49 @@ +/** + * STEP 1: Basic DI Container + * + * Learning Goals: + * - Understand the core concept of a container + * - Register and resolve simple dependencies + * - Manual instantiation + */ + +export class SimpleContainer { + private readonly _services: Map; + + constructor() { + this._services = new Map(); + } + + /* Register a service + * @param {string} name - Service name/identifier + * @param {*} instance - The service instance + */ + public register(name: string, instance) { + if (this._services.has(name)) { + throw new Error(`Service [${name}] is already registered`); + } + + this._services.set(name, instance); + console.log(`[SimpleContainer] Registered [${name}]`); + } + + /** + * Resolve (retrieve) a service + * @param {string} name - Service name/identifier + * @returns {*} The service instance + */ + public resolve(name) { + if (!this._services.has(name)) { + throw new Error(`Service [${name}] is not registered`); + } + + return this._services.get(name); + } + + /** + * Check if a service is registered + */ + public has(name): boolean { + return this._services.has(name); + } +} diff --git a/DependencyInjection/src/Step1/UserService.ts b/DependencyInjection/src/Step1/UserService.ts new file mode 100644 index 0000000..ddfcaca --- /dev/null +++ b/DependencyInjection/src/Step1/UserService.ts @@ -0,0 +1,20 @@ +import {Logger} from './Logger'; +import {Database} from './Database'; + +export class UserService { + private _logger: Logger; + private _database: Database; + + constructor(logger: Logger, database: Database) { + this._logger = logger; + this._database = database; + } + + public getUser(id: number) { + this._logger.info(`Getting user with id [${id}]`); + + const result = this._database.query(`SELECT * FROM users WHERE id = ${id}`); + + return result; + } +} diff --git a/DependencyInjection/src/Step1/index.ts b/DependencyInjection/src/Step1/index.ts new file mode 100644 index 0000000..e26d1a0 --- /dev/null +++ b/DependencyInjection/src/Step1/index.ts @@ -0,0 +1,56 @@ +import {Logger} from './Logger'; +import {Database} from './Database'; +import {UserService} from './UserService.ts'; +import {SimpleContainer} from './SimpleContainer'; + +console.log('===== STEP 1: Basic DI Container =====\n'); + +// Create the Container +const container = new SimpleContainer(); + +// Create instances manually +const logger = new Logger('STEP 1'); +const database = new Database(); +const userService = new UserService(logger, database); + +// Register them in the container +container.register('logger', logger); +container.register('database', database); +container.register('userService', userService); + +console.log('\n ---- User Services ---- \n'); + +const resolvedLogger = container.resolve('logger'); +resolvedLogger.info('Hello from DI container'); + +const resolvedUserService = container.resolve('userService'); +const user = resolvedUserService.getUser(123); + +console.log(`Result [${user}]`); + +// THE PROBLEM WITH THIS APPROACH +console.log('\n--- Problems with Step 1 ---\n'); +console.log('āŒ We still create instances manually'); +console.log('āŒ We must register them in correct order (dependencies first)'); +console.log("āŒ Container doesn't know about dependencies"); +console.log('āŒ No automatic instantiation'); +console.log('\nšŸ‘‰ Step 2 will solve this with constructor injection!\n'); + +// ============================================================================ +// KEY LEARNINGS +// ============================================================================ +/** + * What we learned: + * + * 1. A DI container is essentially a registry/map of services + * 2. We can register services with a name/key + * 3. We can resolve (retrieve) services by name + * 4. But we still create objects manually - not very useful yet! + * + * Next step: Make the container smart enough to: + * - Understand dependencies + * - Automatically instantiate classes + * - Resolve dependency chains + */ + +export {}