A simple todo list application built with Next.js 15, React 19, TypeScript, and Zustand, following the BYO-DPP (Build Your Own Digital Product Passport) enterprise architecture pattern.
This project demonstrates the BYO-DPP architecture pattern - an enterprise-grade implementation of Clean Architecture with the Invoker Pattern for runtime dependency resolution.
┌─────────────────────────────────────────────┐
│ UI Layer │
│ (React Components, Hooks) │
│ ↓ uses ServiceInvoker │
├─────────────────────────────────────────────┤
│ Application Layer │
│ (Services, Repositories, DTOs) │
│ ↓ Services use RepositoryInvoker │
├─────────────────────────────────────────────┤
│ Infrastructure Layer │
│ (Factories, Adapters) │
│ ↓ Registers dependencies │
├─────────────────────────────────────────────┤
│ Domain Layer │
│ (Entities, Business Logic) │
│ ↓ depends on NOTHING │
└─────────────────────────────────────────────┘
- Invoker Pattern - Runtime dependency resolution through invokers
- Service-Oriented - Services replace traditional use-cases for better cohesion
- DTO Mapping - Clean separation between domain entities and data transfer objects
- Dependency Injection - Centralized dependency management via providers and factories
Dependencies flow inward: Each layer depends only on layers below it. The domain layer has zero external dependencies.
dpp-todolist/
├── domain/ # Core business logic (zero dependencies)
│ ├── entities/
│ │ └── Todo.ts # Todo entity with business rules
│ ├── enums/
│ │ └── todo-status.enum.ts # Domain enumerations
│ ├── interfaces/
│ │ ├── ITodoRepository.ts # Repository contract
│ │ └── dependency-invoker.interface.ts
│ ├── invokers/
│ │ └── logic.invoker.ts # Domain logic invoker
│ └── types/
│ └── TodoStatus.ts # Domain types
│
├── application/ # Application layer (depends on domain only)
│ ├── services/ # Services (replace use-cases)
│ │ ├── service.invoker.ts # Service invoker
│ │ └── todo/
│ │ ├── todo.service.ts # Todo service (all operations)
│ │ ├── in-dtos/ # Input DTOs
│ │ ├── out-dtos/ # Output DTOs
│ │ └── mappers/
│ │ └── todo.mapper.ts # Entity ↔ DTO mapper
│ ├── repositories/ # Repository implementations
│ │ ├── repository.ts # Base repository class
│ │ ├── repository.invoker.ts # Repository invoker
│ │ └── todos/
│ │ └── InMemoryTodoRepository.ts
│ ├── providers/
│ │ └── app-dependency.provider.ts # Dependency registration
│ ├── interfaces/
│ │ ├── dependency-invoker.interface.ts
│ │ └── repository.interface.ts
│ ├── store/
│ │ └── TodoStore.ts # Zustand state management
│ └── in-dtos/
│ ├── CreateTodoRequest.ts
│ └── UpdateTodoRequest.ts
│
├── infrastructure/ # Technical implementations
│ └── factories/
│ └── app-dependency.factory.ts # Dependency factory
│
├── components/ # UI components
│ ├── feature/
│ │ └── todos/
│ │ ├── CreateTodoForm.tsx
│ │ └── TodoList.tsx
│ └── shared/
│ └── TodoItem.tsx
│
├── hooks/
│ └── useTodos.ts # Custom hook using invoker pattern
│
└── app/
└── page.tsx # Main page
- Todo Entity: Contains business rules like validation, status changes
- No external dependencies: Can be tested without any framework
- Business methods:
complete(),markAsPending(),updateTitle(), etc. - Enums: TodoStatus enum for type-safe status values
- TodoService: Centralized service for all todo operations
- DTO Mappers: Convert between domain entities and DTOs
- Repository Invoker: Runtime dependency resolution for repositories
- Service Invoker: Runtime dependency resolution for services
- Repositories in Application: Following BYO-DPP pattern (not in infrastructure!)
- AppDependencyFactory: Central factory for accessing dependencies
- Providers: Register and initialize all dependencies
- Clean separation: No business logic, only wiring
- React Components: Built with Tailwind CSS
- Custom Hooks: Use service invoker pattern to access business logic
- Separation of Concerns: Components are simple and focused
| Category | Technology |
|---|---|
| Framework | Next.js 15 (App Router) |
| UI Library | React 19 |
| Language | TypeScript 5.7 |
| State Management | Zustand 5 |
| Styling | Tailwind CSS 4 |
| Architecture | BYO-DPP (Clean Architecture + Invoker Pattern) |
- Node.js 18+
- pnpm (recommended) or npm
cd ~/tvs/dpp-todolist
pnpm installpnpm devOpen http://localhost:3000 to see the application.
pnpm build
pnpm startUser fills form
└─> CreateTodoForm.tsx (UI Layer)
└─> useTodos.createTodo() (Hook)
└─> ServiceInvoker.invoke('todoService') (Invoker)
└─> TodoService.createTodo() (Application)
└─> Todo entity created (Domain)
└─> TodoMapper.toResponseDTO() (Mapper)
└─> InMemoryTodoRepository.create() (Application)
└─> Zustand store updated
└─> UI re-renders
Instead of direct instantiation, dependencies are resolved at runtime:
// Get service via invoker
const factory = AppDependencyFactory.getInstance();
const serviceInvoker = factory.getServiceInvoker();
const todoService = serviceInvoker.invoke<TodoService>('todoService');
// Use service
const todoDTO = await todoService.createTodo({ title: 'New Todo', description: '' });Benefits:
- Runtime flexibility - swap implementations without code changes
- Centralized dependency management
- Easy to mock for testing
- Supports plugin architectures
- Enterprise-Ready: Invoker pattern supports complex dependency graphs
- Testability: Pure business logic, easy mocking via invokers
- Flexibility: Swap implementations at runtime
- Maintainability: Service-oriented with clear boundaries
- Scalability: Services can have sub-services, supports large systems
- Team Collaboration: Clear layers and patterns
The TodoService centralizes all todo operations:
// application/services/todo/todo.service.ts
export class TodoService {
constructor(private readonly todoRepository: ITodoRepository) {}
async createTodo(dto: CreateTodoDTO): Promise<TodoResponseDTO> {
const todo = new Todo(...); // Create domain entity
const created = await this.todoRepository.create(todo);
return TodoMapper.toResponseDTO(created); // Map to DTO
}
async getAllTodos(): Promise<TodoResponseDTO[]> {
const todos = await this.todoRepository.findAll();
return todos.map(TodoMapper.toResponseDTO);
}
}All operations in one cohesive service, with clean DTO mapping!
The Todo entity contains all business rules:
// domain/entities/Todo.ts
export class Todo {
complete(): void {
if (!this.canBeCompleted()) {
throw new Error('Todo cannot be completed');
}
this.status = TODO_STATUS.COMPLETED;
this.updatedAt = new Date();
}
markAsPending(): void {
this.status = TODO_STATUS.PENDING;
this.updatedAt = new Date();
}
canBeCompleted(): boolean {
return this.status === TODO_STATUS.PENDING &&
this.title.trim().length > 0;
}
}No dependencies on React, Next.js, or any external library!
The project uses TypeScript path aliases for clean imports:
import { Todo } from '@/domain/entities/Todo';
import { TodoService } from '@/application/services/todo/todo.service';
import { InMemoryTodoRepository } from '@/application/repositories/todos/InMemoryTodoRepository';
import { AppDependencyFactory } from '@/infrastructure/factories/app-dependency.factory';| Aspect | Traditional Clean Arch | BYO-DPP Pattern |
|---|---|---|
| Business Logic | Use Cases | Services |
| Dependency Resolution | Direct injection | Invoker pattern |
| Repository Location | Infrastructure layer | Application layer |
| DTO Handling | Manual mapping | Dedicated mappers |
| Flexibility | Compile-time | Runtime |
- ARCHITECTURE.md - Detailed Onion Architecture explanation
- FOLDER_ARCHITECTURE.md - Complete BYO-DPP structure guide
- Replace in-memory repository with API client
- Add unit tests for domain entities
- Add integration tests for services
- Implement local storage persistence
- Add todo categories and tags
- Add date picker for due dates
- Add filtering and sorting
- Implement authentication with user-specific todos
This architecture is based on:
- BYO-DPP Reference Implementation
- Clean Architecture
- Onion Architecture
- DDD (Domain-Driven Design)
- Dependency Inversion Principle
MIT
Built with BYO-DPP architecture - Enterprise-grade Clean Architecture with Invoker Pattern