This document summarizes the implementation of four major refactoring tasks for the Soroban Ajo project.
Four refactoring issues have been successfully implemented:
- #491: Improve Error Handling Consistency
- #492: Split Large Components into Smaller Ones
- #493: Implement Dependency Injection
- #501: Migrate to Monorepo Structure
All changes have been committed to the branch: refactor/491-492-493-501-error-handling-di-components-monorepo
Standardize error handling across the backend with consistent error types, messages, and HTTP status codes.
-
backend/src/errors/ValidationError.ts- Specialized error class for validation failures
- Factory methods for common validation scenarios
- Methods:
fromZodErrors(),missingField(),invalidFormat(),outOfRange()
-
backend/src/errors/AuthenticationError.ts- Specialized error class for authentication failures
- Specific error patterns: token expired, invalid, missing, MFA required
- Methods:
invalidCredentials(),tokenExpired(),tokenInvalid(),missingToken(),sessionExpired(),mfaRequired(),invalidMfaCode()
-
backend/src/errors/BlockchainError.ts- Specialized error class for Soroban/contract-related failures
- Handles contract execution, transactions, balance, network errors
- Methods:
contractExecutionFailed(),transactionFailed(),insufficientBalance(),invalidContractState(),networkError(),rpcError(),timeout(),invalidParameters()
-
backend/src/utils/errorMapper.ts- Centralized error mapping utility
- Converts various error types to AppError
- Methods:
mapError(),mapContractError(),mapAuthError(),getStatusCode(),getErrorCode(),isOperational(),formatErrorResponse()
backend/src/middleware/errorHandler.ts- Updated to use ErrorMapper for consistent error handling
- Added error code logging for debugging
- Improved error context in responses
- ✅ Consistent error handling across the application
- ✅ Proper HTTP status codes for all error types
- ✅ Error codes for client-side error handling
- ✅ Detailed error information for debugging
- ✅ Easy to extend with new error types
import { ValidationError, AuthenticationError, BlockchainError } from '../errors'
// Validation error
throw ValidationError.missingField('groupName')
// Authentication error
throw AuthenticationError.tokenExpired()
// Blockchain error
throw BlockchainError.insufficientBalance(100, 50, 'USDC')Break down large components (>300 lines) into smaller, focused components following single responsibility principle.
New Directory Structure:
frontend/src/components/GroupCreationForm/
├── index.tsx # Main component (orchestrator)
├── types.ts # Type definitions
├── validation.ts # Validation logic
├── FormComponents.tsx # Reusable form components
├── BasicInfoStep.tsx # Step 1: Basic Information
├── SettingsStep.tsx # Step 2: Group Settings
├── MembersStep.tsx # Step 3: Invite Members
└── ReviewStep.tsx # Step 4: Review & Create
-
types.tsGroupFormDatainterfaceFormErrorsinterfaceGroupCreationFormPropsinterface
-
validation.tsvalidateField()- Validates individual fieldsvalidateStep()- Validates a specific stepvalidateForm()- Validates entire form
-
FormComponents.tsxFormFieldcomponent - Reusable form field with error displayErrorSummarycomponent - Error summary display
-
BasicInfoStep.tsx- Step 1: Group name and description
- Focused on basic information collection
-
SettingsStep.tsx- Step 2: Cycle length, contribution amount, max members
- Focused on group configuration
-
MembersStep.tsx- Step 3: Member invitation
- Focused on member management
-
ReviewStep.tsx- Step 4: Review and create
- Focused on final confirmation
-
index.tsx- Main component orchestrating all steps
- Handles form state and submission
- Manages step navigation
- Original
GroupCreationForm.tsxupdated to re-export from new structure - No breaking changes for existing imports
- ✅ Improved code organization and readability
- ✅ Single responsibility principle
- ✅ Easier to test individual components
- ✅ Better code reusability
- ✅ Easier to maintain and extend
- ✅ Atomic design principles
// Still works as before
import { GroupCreationForm } from '@/components/GroupCreationForm'
// Or import specific components
import { BasicInfoStep } from '@/components/GroupCreationForm/BasicInfoStep'
import { validateField } from '@/components/GroupCreationForm/validation'Implement dependency injection pattern to improve testability and reduce tight coupling between modules.
-
backend/src/di/container.tsDIContainerclass for service registration and resolution- Support for factory functions and class constructors
- Singleton pattern implementation
- Methods:
register(),registerClass(),registerInstance(),resolve(),has(),clear(),getKeys()
-
backend/src/di/types.tsTYPESsymbol definitions for all servicesIServiceMapinterface for type-safe service resolution- Services: Logger, Config, Database, SorobanService, GroupService, UserService, etc.
-
backend/src/di/bindings.tssetupDependencies()- Initialize all service bindingsgetService()- Resolve service from containerregisterService()- Register custom service- Service registration for all major services
-
backend/src/di/index.ts- Exports all DI-related utilities
-
backend/src/di/example.controller.ts- Example controller demonstrating DI usage
- Shows how to inject services in controllers
- ✅ Loose coupling between modules
- ✅ Easy to test with mock services
- ✅ Centralized service configuration
- ✅ Singleton management
- ✅ Dependency resolution
- ✅ Improved maintainability
import { getService, TYPES, setupDependencies } from '../di'
// Initialize DI container
setupDependencies()
// In controller
const groupService = getService(TYPES.GroupService)
const groups = await groupService.getAllGroups()
// In tests
import { registerService, TYPES } from '../di'
const mockService = { /* mock implementation */ }
registerService(TYPES.GroupService, mockService)Restructure project into proper monorepo using Turborepo for better code sharing and build optimization.
-
turbo.json- Turborepo configuration
- Build pipeline definition
- Task dependencies and caching
- Global dependencies and environment variables
-
packages/shared/package.json- Shared package configuration
- Dependencies: zod
-
packages/shared/tsconfig.json- TypeScript configuration for shared package
-
packages/shared/src/types.ts- Common types: Group, User, Transaction, Notification, Achievement, UserStats
- API response types: ApiResponse, PaginatedResponse, ApiError
-
packages/shared/src/schemas.ts- Zod validation schemas
- GroupCreateSchema, UserCreateSchema, TransactionCreateSchema, PaginationSchema
-
packages/shared/src/utils.ts- Utility functions: formatCurrency, formatDate, truncateAddress, generateId, delay, retryWithBackoff
-
packages/shared/src/index.ts- Shared package exports
-
MONOREPO_STRUCTURE.md- Comprehensive documentation
- Package descriptions
- Getting started guide
- Directory structure
- Development workflow
soroban-ajo/
├── packages/
│ ├── shared/ # Shared types and utilities
│ ├── frontend/ # Next.js application
│ ├── backend/ # Express API server
│ └── contracts/ # Soroban contracts
├── turbo.json # Turborepo configuration
└── MONOREPO_STRUCTURE.md # Documentation
- build: Builds all packages with caching
- dev: Runs development servers (no caching)
- lint: Lints all packages
- type-check: Type checks all packages
- test: Runs tests with caching
- clean: Cleans build artifacts
- ✅ Code sharing between frontend and backend
- ✅ Optimized build pipeline with caching
- ✅ Independent package deployments
- ✅ Consistent types across packages
- ✅ Reduced code duplication
- ✅ Better dependency management
// In frontend
import { Group, formatCurrency } from '@soroban-ajo/shared'
// In backend
import { GroupCreateSchema, Group } from '@soroban-ajo/shared'
// Run all dev servers
npm run dev
// Run specific package
npm run dev --filter=@soroban-ajo/frontend
// Build all packages
npm run build- ✅ 5 new error handling files
- ✅ 5 new DI container files
- ✅ Improved error middleware
- ✅ Better error consistency
- ✅ 8 new component files (GroupCreationForm split)
- ✅ Improved component organization
- ✅ Better code reusability
- ✅ Turborepo configuration
- ✅ Shared package with types and utilities
- ✅ Comprehensive documentation
# Test error mapping
npm run test --filter=@soroban-ajo/backend -- errorMapper.test.ts
# Test error middleware
npm run test --filter=@soroban-ajo/backend -- errorHandler.test.ts# Test individual steps
npm run test --filter=@soroban-ajo/frontend -- BasicInfoStep.test.tsx
npm run test --filter=@soroban-ajo/frontend -- SettingsStep.test.tsx
# Test validation
npm run test --filter=@soroban-ajo/frontend -- validation.test.ts# Test DI container
npm run test --filter=@soroban-ajo/backend -- container.test.ts
# Test service resolution
npm run test --filter=@soroban-ajo/backend -- bindings.test.ts# Build all packages
npm run build
# Type check all packages
npm run type-check
# Lint all packages
npm run lint- Update existing services to use DI container
- Migrate other large components following the same pattern
- Add comprehensive tests for all new code
- Update documentation with new patterns
- Configure CI/CD for monorepo structure
- Set up remote caching for Turborepo (optional)
Branch Name: refactor/491-492-493-501-error-handling-di-components-monorepo
Commits:
3d68a2f- feat(#491): Improve error handling consistency81a4b91- refactor(#492): Split GroupCreationForm into smaller components132d0e3- feat(#493): Implement Dependency Injection patternac4acc0- feat(#501): Migrate to Monorepo Structure with Turborepo
All four refactoring tasks have been successfully implemented with:
- ✅ Consistent error handling across the backend
- ✅ Smaller, focused components following atomic design
- ✅ Dependency injection for improved testability
- ✅ Monorepo structure with Turborepo for better code sharing
The codebase is now more maintainable, testable, and scalable.