Use Cases Example
This example demonstrates how IoC Arise handles classes that do not implement interfaces but are used as dependencies (like use cases in clean architecture). You’ll learn how the container automatically includes these classes to support proper dependency injection.
Project Structure
Section titled “Project Structure”Directoryuse-cases-example/
- ioc.config.json
- container.gen.ts
Directoryrepositories/
- IUserRepository.ts
- UserRepository.ts
Directoryservices/
- IEmailService.ts
- EmailService.ts
- IApplicationService.ts
- ApplicationService.ts
Directoryuse-cases/
- CreateUserUseCase.ts
- GetUserUseCase.ts
- UserController.ts
Key Points
Section titled “Key Points”- Interface-based classes: Repository and service classes implement interfaces (
IUserRepository
,IEmailService
,IApplicationService
) - Non-interface classes: Use case classes (
CreateUserUseCase
,GetUserUseCase
,UserController
) do NOT implement interfaces - Dependency inclusion: IoC Arise includes non-interface classes because they are used as dependencies by other classes
- Automatic detection: The tool automatically detects and includes all classes that participate in dependency injection
Generated Container Structure
Section titled “Generated Container Structure”The generated container has a coreModule
structure:
export const container = { coreModule: { // Interface-based access (recommended) IUserRepository: UserRepository, IEmailService: EmailService, IApplicationService: ApplicationService,
// Direct class access CreateUserUseCase: CreateUserUseCase, GetUserUseCase: GetUserUseCase, UserController: UserController }};
Usage Examples
Section titled “Usage Examples”Using Direct Container Access
Section titled “Using Direct Container Access”import { container } from './container.gen';
// Access services via interfacesconst userRepo = container.coreModule.IUserRepository;const emailService = container.coreModule.IEmailService;const appService = container.coreModule.IApplicationService;
// Access use cases directlyconst createUserUseCase = container.coreModule.CreateUserUseCase;const getUserUseCase = container.coreModule.GetUserUseCase;const userController = container.coreModule.UserController;
// Run the applicationawait appService.runUserOperations();
Using the inject() Function
Section titled “Using the inject() Function”import { inject } from './container.gen';
// Inject services by interfaceconst userRepo = inject('coreModule.IUserRepository');const emailService = inject('coreModule.IEmailService');const appService = inject('coreModule.IApplicationService');
// Inject use cases by class nameconst createUserUseCase = inject('coreModule.CreateUserUseCase');const userController = inject('coreModule.UserController');
// Example usageawait userController.createUser('Alice Johnson', 'alice@example.com');await userController.getUser('1');
Service Definitions
Section titled “Service Definitions”User Entity and Repository Interface
Section titled “User Entity and Repository Interface”export interface IUserRepository { findById(id: string): Promise<User | null>; save(user: User): Promise<void>; findAll(): Promise<User[]>;}
export interface User { id: string; name: string; email: string;}
Email Service Interface
Section titled “Email Service Interface”export interface IEmailService { sendEmail(to: string, subject: string, body: string): Promise<void>; sendWelcomeEmail(userEmail: string, userName: string): Promise<void>;}
Use Case WITHOUT Interface
Section titled “Use Case WITHOUT Interface”This is a key feature - IoC Arise can include classes that don’t implement interfaces but are used as dependencies:
import { IUserRepository } from '../repositories/IUserRepository';import { IEmailService } from '../services/IEmailService';
// This is a use case class that does NOT implement an interface// It should be included in the container because it's used as a dependency
export class CreateUserUseCase { constructor( private userRepository: IUserRepository, private emailService: IEmailService ) {}
async execute(name: string, email: string): Promise<void> { const user = { id: Date.now().toString(), name, email };
await this.userRepository.save(user); await this.emailService.sendWelcomeEmail(user.email, user.name);
console.log(`User created: ${user.name} (${user.email})`); }}
Another Use Case WITHOUT Interface
Section titled “Another Use Case WITHOUT Interface”import { IUserRepository } from '../repositories/IUserRepository';
export class GetUserUseCase { constructor(private userRepository: IUserRepository) {}
async execute(userId: string): Promise<void> { const user = await this.userRepository.findById(userId); if (user) { console.log(`Found user: ${user.name} (${user.email})`); } else { console.log(`User with ID ${userId} not found`); } }}
Controller Using Use Cases
Section titled “Controller Using Use Cases”import { CreateUserUseCase } from './CreateUserUseCase';import { GetUserUseCase } from './GetUserUseCase';
// This controller uses use cases as dependencies// It does NOT implement an interface but should be included because it's used as a dependencyexport class UserController { constructor( private createUserUseCase: CreateUserUseCase, private getUserUseCase: GetUserUseCase ) {}
async createUser(name: string, email: string): Promise<void> { console.log('UserController: Creating user...'); await this.createUserUseCase.execute(name, email); }
async getUser(userId: string): Promise<void> { console.log('UserController: Getting user...'); await this.getUserUseCase.execute(userId); }}
Post-Construction Initialization
Section titled “Post-Construction Initialization”The onInit()
function is exported from the generated container.gen.ts file, not a method in your classes:
import { onInit } from './container.gen';
// The onInit function is called automatically when first accessing the container// You can customize it for post-construction logic:export function onInit(): void { console.log('Container initialized'); // Add your initialization logic here}
For services that need additional setup:
export class ApplicationService { private createUserUseCase!: CreateUserUseCase; private getUserUseCase!: GetUserUseCase; private userController!: UserController;
initializeDependencies() { this.createUserUseCase = inject('coreModule.CreateUserUseCase'); this.getUserUseCase = inject('coreModule.GetUserUseCase'); this.userController = inject('coreModule.UserController'); console.log('ApplicationService initialized with use cases and controller'); }
async processUserRegistration(name: string, email: string) { // Use the controller for user creation await this.userController.createUser(name, email); console.log(`User registration processed for ${name}`); }
async processUserLookup(userId: string) { // Use the use case directly await this.getUserUseCase.execute(userId); console.log(`User lookup processed for ${userId}`); }}
Configuration
Section titled “Configuration”{ "source": ".", "output": "container.gen.ts", "interface": "I[A-Z].*", "exclude": [ "**/*.test.ts", "**/*.spec.ts" ], "verbose": true}
Running the Example
Section titled “Running the Example”-
Generate the container:
Terminal window npx @notjustcoders/ioc-arise generate -
The generated container includes all classes that are either:
- Implementing interfaces (repositories and services), OR
- Used as dependencies by other classes (use cases and controllers)
-
Use either direct container access or the
inject()
function to retrieve dependencies -
The container handles all dependency injection automatically with proper singleton management
Benefits of Non-Interface Class Support
Section titled “Benefits of Non-Interface Class Support”- Clean Architecture Support: Perfect for use cases that don’t need interfaces
- Automatic Detection: IoC Arise automatically includes classes used as dependencies
- Type Safety: Full TypeScript support for both interface-based and direct class injection
- Flexibility: No forced interface implementation for simple classes
- Dependency Validation: Ensures all participating classes are properly registered
This example shows how IoC Arise intelligently handles both interface-based and direct class dependencies, making it perfect for clean architecture patterns where use cases and controllers don’t always need interface contracts!