Simple Modules Example
This example demonstrates IoC Arise’s module system with two separate modules that have cross-module dependencies. You’ll learn how to organize your code into logical modules while maintaining type-safe dependency injection across module boundaries.
Project Structure
Section titled “Project Structure”Directorysimple-modules/
Directoryuser/
- User.ts User entity
- IUserRepository.ts User repository interface
- UserRepository.ts User repository implementation
- IUserService.ts User service interface
- UserService.ts User service implementation
Directorytodo/
- Todo.ts Todo entity
- ITodoRepository.ts Todo repository interface
- TodoRepository.ts Todo repository implementation
- ITodoService.ts Todo service interface
- TodoService.ts Todo service implementation (cross-module dependency)
- container.gen.ts Main container aggregating modules
- demo.ts Demonstration of module interaction
- ioc.config.json IoC configuration with module definitions
- README.md This file
Key Features
Section titled “Key Features”1. Module Organization
Section titled “1. Module Organization”Each module encapsulates related functionality:
UserModule:
User
entity with user dataIUserRepository
&UserRepository
for data accessIUserService
&UserService
for business logic
TodoModule:
Todo
entity with todo dataITodoRepository
&TodoRepository
for data accessITodoService
&TodoService
for business logic
2. Cross-Module Dependencies
Section titled “2. Cross-Module Dependencies”The TodoService demonstrates cross-module dependency by depending on UserRepository:
export class TodoService implements ITodoService { constructor( private todoRepository: ITodoRepository, // Same module (TodoModule) private userRepository: IUserRepository, // Different module (UserModule)! ) {}
async createTodo(title: string, userId: string): Promise<Todo> { // Validate user exists using cross-module dependency const user = await this.userRepository.findById(userId); if (!user) { throw new Error(`User with ID ${userId} not found`); } // Create todo logic... }}
3. Automatic Dependency Resolution
Section titled “3. Automatic Dependency Resolution”IoC Arise automatically:
- Discovers dependencies across modules
- Creates proper module instantiation order
- Wires everything together in a unified container
Usage with Container
Section titled “Usage with Container”Method 1: Direct Container Access
Section titled “Method 1: Direct Container Access”import { container } from "./container.gen";
// Access user module servicesconst userService = container.userModule.IUserService;const userRepository = container.userModule.IUserRepository;
// Access todo module servicesconst todoService = container.todoModule.ITodoService;const todoRepository = container.todoModule.ITodoRepository;
// Use servicesconst users = await userService.getAllUsers();const userTodos = await todoService.getTodosByUser("1");
Method 2: Using inject() Function (Type-Safe)
Section titled “Method 2: Using inject() Function (Type-Safe)”import { inject } from "./container.gen";
// Access services with full type safety and module pathsconst userService = inject("userModule.IUserService");const todoService = inject("todoModule.ITodoService");
// Create a new todo (with user validation)const newTodo = await todoService.createTodo("Learn IoC Arise", "1");
// Get all usersconst users = await userService.getAllUsers();
Example Workflow
Section titled “Example Workflow”// 1. Get all usersconst users = await userService.getAllUsers();console.log("Available users:", users);
// 2. Create a todo for a specific userconst newTodo = await todoService.createTodo("Complete project", "1");console.log("Created todo:", newTodo);
// 3. Get todos for that userconst userTodos = await todoService.getTodosByUser("1");console.log("User todos:", userTodos);
// 4. Update todo statusconst updatedTodo = await todoService.updateTodo(newTodo.id, undefined, true);console.log("Updated todo:", updatedTodo);
// 5. Try to create todo for non-existent user (will throw error)try { await todoService.createTodo("Invalid todo", "non-existent-user");} catch (error) { console.log("Expected error:", error.message);}
Entity Definitions
Section titled “Entity Definitions”User Entity
Section titled “User Entity”export class User { constructor( public id: string, public name: string, public email: string, ) {}}
Todo Entity
Section titled “Todo Entity”export class Todo { constructor( public id: string, public title: string, public userId: string, public completed: boolean = false, ) {}}
Service Interfaces
Section titled “Service Interfaces”IUserService
Section titled “IUserService”export interface IUserService { getAllUsers(): Promise<User[]>; getUserById(id: string): Promise<User | undefined>; createUser(name: string, email: string): Promise<User>; updateUser( id: string, name?: string, email?: string, ): Promise<User | undefined>; deleteUser(id: string): Promise<boolean>;}
ITodoService
Section titled “ITodoService”export interface ITodoService { getAllTodos(): Promise<Todo[]>; getTodoById(id: string): Promise<Todo | undefined>; getTodosByUser(userId: string): Promise<Todo[]>; createTodo(title: string, userId: string): Promise<Todo>; updateTodo( id: string, title?: string, completed?: boolean, ): Promise<Todo | undefined>; deleteTodo(id: string): Promise<boolean>;}
Cross-Module Dependencies Example
Section titled “Cross-Module Dependencies Example”The Todo module depends on the User module through the IUserRepository
interface:
import { Todo } from "./Todo";import { ITodoService } from "./ITodoService";import { ITodoRepository } from "./ITodoRepository";import { IUserRepository } from "../user/IUserRepository";
/** * @scope singleton */export class TodoService implements ITodoService { constructor( private todoRepository: ITodoRepository, private userRepository: IUserRepository, ) {}
async getTodosByUser(userId: string): Promise<Todo[]> { // Verify user exists before getting todos const user = await this.userRepository.findById(userId); if (!user) { throw new Error(`User with id ${userId} not found`); } return this.todoRepository.findByUserId(userId); }
async createTodo(title: string, userId: string): Promise<Todo> { // Verify user exists before creating todo const user = await this.userRepository.findById(userId); if (!user) { throw new Error(`User with id ${userId} not found`); } return this.todoRepository.create(title, userId); }
// ... other methods}
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 inject() is first used// You can modify it in the generated container.gen.ts file for custom initialization
For services that need cross-module dependencies setup:
export class ApplicationCoordinator { private userService!: IUserService; private todoService!: ITodoService;
initializeCrossModuleDependencies() { this.userService = inject("userModule.IUserService"); this.todoService = inject("todoModule.ITodoService"); console.log( "ApplicationCoordinator initialized with cross-module dependencies", ); }
async setupUserWithTodos(name: string, email: string, todoTitles: string[]) { // Create user using UserModule service const user = await this.userService.createUser(name, email);
// Create todos using TodoModule service (with cross-module dependency) const todos = []; for (const title of todoTitles) { const todo = await this.todoService.createTodo(title, user.id); todos.push(todo); }
console.log(`Created user ${user.name} with ${todos.length} todos`); return { user, todos }; }
async getUserStats(userId: string) { const user = await this.userService.getUser(userId); const userTodos = await this.todoService.getTodosByUser(userId);
return { user: user?.name || "Unknown", totalTodos: userTodos.length, completedTodos: userTodos.filter((t) => t.completed).length, }; }}
IoC Configuration
Section titled “IoC Configuration”The ioc.config.json
defines modules and their patterns:
{ "source": ".", "output": "container.gen.ts", "modules": { "UserModule": ["user/**/*"], "TodoModule": ["todo/**/*"] }}
This configuration:
- Groups files in
user/
directory intoUserModule
- Groups files in
todo/
directory intoTodoModule
- Automatically resolves cross-module dependencies
- Generates separate module containers with proper dependency injection order
Benefits of Modular Architecture
Section titled “Benefits of Modular Architecture”- Modular Organization: Keep related code together in logical modules
- Cross-Module Dependencies: Services can safely depend on other modules
- Type Safety: Full TypeScript support across module boundaries
- Testability: Easy to mock entire modules or individual services
- Scalability: Add new modules without affecting existing ones
- Clean Architecture: Clear separation of concerns between domains
- Dependency Validation: Automatic validation of cross-module dependencies
This example shows how IoC Arise’s module system enables clean separation of concerns while maintaining the ability to share functionality across domains, making it perfect for scalable applications!