Layered Architecture
The Nexgent backend follows a layered architecture inspired by Domain-Driven Design (DDD). This pattern separates concerns into distinct layers, making the codebase maintainable, testable, and scalable.
Layer Overview
┌─────────────────────────────────────────────────────────────────────┐
│ API Layer │
│ Routes, Handlers, Request Validation, Response Formatting │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Domain Layer │
│ Business Logic, Services, Domain Events, Repositories (Interfaces) │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Infrastructure Layer │
│ Database, Cache, External APIs, Queue, WebSocket │
└─────────────────────────────────────────────────────────────────────┘API Layer (src/api/)
The API layer handles HTTP requests and responses. It is responsible for:
- Routing - Mapping URLs to handlers
- Validation - Validating request bodies using Zod schemas
- Authentication - Applying auth middleware
- Response Formatting - Consistent JSON responses
Structure
src/api/
├── index.ts # Route aggregator
└── v1/ # Versioned API (v1)
├── agents/ # Agent CRUD endpoints
│ ├── routes.ts # Route definitions
│ └── handlers/ # Request handlers
├── auth/ # Authentication endpoints
├── trading-signals/ # Signal management
├── wallets/ # Wallet operations
├── health/ # Health checks
└── ...Example Route Definition
// src/api/v1/agents/routes.ts
const router = Router();
router.post('/', authenticate, validate(CreateAgentSchema), createAgent);
router.get('/', authenticate, listAgents);
router.get('/:id', authenticate, getAgent);
router.put('/:id', authenticate, validate(UpdateAgentSchema), updateAgent);
router.delete('/:id', authenticate, deleteAgent);All API routes are versioned (/api/v1/) to allow for future API changes without breaking existing clients.
Domain Layer (src/domain/)
The domain layer contains all business logic. Services in this layer are framework-agnostic and can be tested in isolation.
Structure
src/domain/
├── agents/ # Agent management
│ ├── agent-service.ts
│ └── agent.repository.ts (interface)
├── balances/ # Balance tracking
│ ├── balance-service.ts
│ ├── balance-snapshot.service.ts
│ └── balance-snapshot-scheduler.ts
├── positions/ # Position tracking
│ └── position.repository.ts (interface)
├── prices/ # Price updates
│ └── price-update-manager.ts
├── signals/ # Signal processing
│ ├── signal-processor.service.ts
│ ├── agent-eligibility.service.ts
│ ├── execution-tracker.service.ts
│ └── signal-events.ts
├── trading/ # Trade execution
│ ├── trading-executor.service.ts
│ ├── trade-validator.service.ts
│ ├── position-service.ts
│ ├── position-calculator.service.ts
│ ├── stop-loss-manager.service.ts
│ ├── dca-manager.service.ts
│ ├── config-service.ts
│ └── position-events.ts
├── transactions/ # Transaction tracking
│ └── transaction.repository.ts (interface)
└── wallets/ # Wallet management
└── wallet-reset-service.tsDesign Principles
| Principle | Implementation |
|---|---|
| Single Responsibility | Each service handles one domain concept |
| Dependency Injection | Services receive dependencies via constructor |
| Interface Segregation | Repository interfaces define minimal contracts |
| Event-Driven | Services communicate via EventEmitters |
Service Pattern
All domain services follow the singleton pattern with explicit dependency injection:
class TradingExecutor {
constructor(
private readonly agentRepo: IAgentRepository,
private readonly positionRepo: IPositionRepository,
private readonly transactionRepo: ITransactionRepository
) {}
async executePurchase(request: TradeExecutionRequest): Promise<TradeExecutionResult> {
// Business logic here
}
}
// Export singleton with concrete implementations
export const tradingExecutor = new TradingExecutor(
new AgentRepository(),
new PositionRepository(),
new TransactionRepository()
);Infrastructure Layer (src/infrastructure/)
The infrastructure layer handles all external concerns: databases, caches, external APIs, and messaging.
Structure
src/infrastructure/
├── cache/ # Redis caching services
│ ├── redis-client.ts
│ ├── redis-balance-service.ts
│ ├── redis-position-service.ts
│ ├── redis-price-service.ts
│ ├── redis-config-service.ts
│ ├── redis-agent-service.ts
│ ├── redis-token-service.ts
│ ├── idempotency-service.ts
│ └── cache-warmer.ts
├── database/ # Prisma ORM
│ ├── client.ts
│ ├── schema.prisma
│ ├── migrations/
│ └── repositories/
├── external/ # External API integrations
│ ├── jupiter/ # Jupiter Aggregator
│ ├── pyth/ # Pyth price oracle
│ ├── dexscreener/ # Token prices
│ └── solana/ # Solana Web3.js
├── logging/ # Pino logger
├── metrics/ # Prometheus metrics
├── queue/ # BullMQ job queue
├── wallets/ # Wallet management
└── websocket/ # WebSocket serverRepository Implementations
Domain layer defines repository interfaces; infrastructure layer provides implementations:
// Domain layer: Interface
interface IAgentRepository {
findById(id: string): Promise<Agent | null>;
findByUserId(userId: string): Promise<Agent[]>;
create(data: CreateAgentData): Promise<Agent>;
}
// Infrastructure layer: Implementation
class AgentRepository implements IAgentRepository {
async findById(id: string): Promise<Agent | null> {
return prisma.agent.findUnique({ where: { id } });
}
// ...
}Middleware Layer (src/middleware/)
Express middleware for cross-cutting concerns:
src/middleware/
├── auth.ts # JWT authentication
├── api-key-auth.ts # API key authentication
├── rate-limiter.ts # Rate limiting
├── validation.ts # Zod schema validation
├── request-logger.ts # Request logging
└── error-handler.ts # Global error handlingShared Layer (src/shared/)
Backend-specific utilities and constants:
src/shared/
├── constants/ # Redis key patterns, TTLs
├── errors/ # Custom error classes
└── utils/
├── auth/ # JWT, password hashing
├── api-keys/ # API key generation/verification
└── timeout.ts # Timeout utilitiesDependency Flow
Dependencies flow downward only. Upper layers depend on lower layers, never the reverse.
API Layer
│
│ depends on
▼
Domain Layer
│
│ depends on
▼
Infrastructure LayerThe infrastructure layer should never directly depend on the API layer. If an infrastructure component needs to trigger an HTTP response, it should emit an event that the API layer handles.
Testing Strategy
Each layer has a specific testing approach:
| Layer | Testing Approach |
|---|---|
| API Layer | Integration tests with Supertest |
| Domain Layer | Unit tests with mocked dependencies |
| Infrastructure Layer | Integration tests with real services |
// Domain layer unit test example
describe('TradingExecutor', () => {
it('should execute purchase', async () => {
const mockAgentRepo = { findById: jest.fn().mockResolvedValue(mockAgent) };
const executor = new TradingExecutor(mockAgentRepo, mockPositionRepo, mockTransactionRepo);
const result = await executor.executePurchase(request);
expect(result.success).toBe(true);
});
});