Skip to content

Name Collision Example

This example demonstrates how IoC Arise automatically handles classes with identical names across different directories using directory-based prefixing. You’ll learn how the container resolves naming conflicts while maintaining clean separation of concerns across different domains.

  • Directoryname-collision-example/
    • Directoryuser/
      • CreateItemUseCase.ts
      • DeleteItemUseCase.ts
      • GetItemUseCase.ts
      • ListItemsUseCase.ts
      • UpdateItemUseCase.ts
      • UserController.ts
      • UserRepository.ts
    • Directoryproduct/
      • CreateItemUseCase.ts
      • DeleteItemUseCase.ts
      • GetItemUseCase.ts
      • ListItemsUseCase.ts
      • UpdateItemUseCase.ts
      • ProductController.ts
      • ProductRepository.ts
    • Directoryorder/
      • CreateItemUseCase.ts
      • DeleteItemUseCase.ts
      • GetItemUseCase.ts
      • ListItemsUseCase.ts
      • UpdateItemUseCase.ts
      • OrderController.ts
      • OrderRepository.ts
    • container.gen.ts
    • ioc.config.json
    • README.md

Multiple classes have identical names across different domains:

  • Three CreateItemUseCase classes (user, product, order)
  • Three DeleteItemUseCase classes (user, product, order)
  • Three GetItemUseCase classes (user, product, order)
  • Three ListItemsUseCase classes (user, product, order)
  • Three UpdateItemUseCase classes (user, product, order)

IoC Arise automatically resolves name collisions using directory-based prefixing:

  1. Directory Prefix: Classes get prefixed with their directory name

    • user/CreateItemUseCase.tsUserCreateItemUseCase
    • product/CreateItemUseCase.tsProductCreateItemUseCase
    • order/CreateItemUseCase.tsOrderCreateItemUseCase
  2. Unique Import Aliases: Generated imports use unique aliases

    import { CreateItemUseCase as UserCreateItemUseCase } from './user/CreateItemUseCase';
    import { CreateItemUseCase as ProductCreateItemUseCase } from './product/CreateItemUseCase';
    import { CreateItemUseCase as OrderCreateItemUseCase } from './order/CreateItemUseCase';
  3. Predictable Naming: Easy to understand based on directory structure

user/CreateItemUseCase.ts
export class CreateItemUseCase {
constructor(private userRepository: IUserRepository) {}
async execute(itemData: { name: string; description: string }): Promise<void> {
console.log(`Creating user item: ${itemData.name}`);
await this.userRepository.saveUserItem(itemData);
}
}
product/CreateItemUseCase.ts
export class CreateItemUseCase {
constructor(private productRepository: IProductRepository) {}
async execute(itemData: { name: string; price: number; category: string }): Promise<void> {
console.log(`Creating product item: ${itemData.name} - $${itemData.price}`);
await this.productRepository.saveProduct(itemData);
}
}
order/CreateItemUseCase.ts
export class CreateItemUseCase {
constructor(private orderRepository: IOrderRepository) {}
async execute(itemData: { name: string; quantity: number; unitPrice: number }): Promise<void> {
console.log(`Creating order item: ${itemData.name}`);
await this.orderRepository.saveOrderItem(itemData);
}
}
import { container } from './container.gen';
// Access services with automatically prefixed names
const userCreateUseCase = container.coreModule.UserCreateItemUseCase;
const productCreateUseCase = container.coreModule.ProductCreateItemUseCase;
const orderCreateUseCase = container.coreModule.OrderCreateItemUseCase;
// All repositories and controllers are similarly prefixed
const userRepo = container.coreModule.UserRepository;
const productRepo = container.coreModule.ProductRepository;
const orderRepo = container.coreModule.OrderRepository;
const userController = container.coreModule.UserController;
const productController = container.coreModule.ProductController;
const orderController = container.coreModule.OrderController;

Method 2: Using inject() Function (Type-Safe)

Section titled “Method 2: Using inject() Function (Type-Safe)”
import { inject } from './container.gen';
// Access services using directory-prefixed names with full type safety
const userCreateUseCase = inject('coreModule.UserCreateItemUseCase');
const productCreateUseCase = inject('coreModule.ProductCreateItemUseCase');
const orderCreateUseCase = inject('coreModule.OrderCreateItemUseCase');
// Access other similarly named services
const userListUseCase = inject('coreModule.UserListItemsUseCase');
const productListUseCase = inject('coreModule.ProductListItemsUseCase');
const orderListUseCase = inject('coreModule.OrderListItemsUseCase');
// Each service works with its own domain data structure
await userCreateUseCase.execute({
name: 'User Profile',
description: 'User profile information'
});
await productCreateUseCase.execute({
name: 'Laptop',
price: 999.99,
category: 'Electronics'
});
await orderCreateUseCase.execute({
name: 'Order Item',
quantity: 2,
unitPrice: 49.99
});
// List items from each domain
const userItems = await userListUseCase.execute();
const productItems = await productListUseCase.execute();
const orderItems = await orderListUseCase.execute();
console.log('User items:', userItems);
console.log('Product items:', productItems);
console.log('Order items:', orderItems);

You can use post-construction injection for cross-domain dependencies:

// Example: Order service that validates products
export class OrderCreateItemUseCase {
private productRepository: IProductRepository;
constructor(private orderRepository: IOrderRepository) {}
// Would be called if this was in the container's onInit function
initializeCrossDomainDependencies(): void {
this.productRepository = inject('coreModule.ProductRepository');
}
async execute(itemData: { name: string; quantity: number; unitPrice: number }): Promise<void> {
// Validate product exists before creating order
const product = await this.productRepository.getProduct(itemData.name);
if (!product) {
throw new Error(`Product ${itemData.name} not found`);
}
await this.orderRepository.saveOrderItem(itemData);
}
}

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 inject() is first used
// You can modify it in the generated container.gen.ts file for custom initialization
{
"srcDir": ".",
"outputFile": "container.gen.ts"
}

This minimal configuration:

  • Scans the current directory for classes
  • Automatically detects name collisions
  • Applies directory-based prefixing to resolve conflicts
  • Generates a single coreModule container
Original File PathGenerated Service Name
user/CreateItemUseCase.tsUserCreateItemUseCase
product/CreateItemUseCase.tsProductCreateItemUseCase
order/CreateItemUseCase.tsOrderCreateItemUseCase
user/UserController.tsUserController
product/ProductController.tsProductController
order/OrderController.tsOrderController
  1. Automatic Resolution: IoC Arise automatically detects and resolves name collisions
  2. Predictable Naming: Directory-based prefixing is easy to understand and predict
  3. Type Safety: Full TypeScript support with proper type inference for all prefixed names
  4. Clean Separation: Each domain maintains its own classes with identical names
  5. No Manual Configuration: Works automatically without additional configuration
  6. Maintainable: Clear naming convention based on directory structure
  7. Scalable: Easy to add new domains without worrying about name conflicts

This example shows how IoC Arise intelligently handles naming conflicts, making it perfect for large applications with multiple domains that naturally have similar operations and naming patterns!