learning_ai_common_plat/AI.dev/SKILLS/architecture-patterns.md

488 lines
14 KiB
Markdown

# Architecture Patterns Skill
**Description**: Common architectural patterns and structures used across the projects.
## When to Use
- Designing new services or features
- Onboarding to the codebase
- Making architectural decisions
- Understanding system design
## Core Architectures
### 1. Microservices Architecture
```
┌─────────────────┐ ┌─────────────────┐
│ Frontend │ │ Frontend │
│ (Next.js) │ │ (Next.js) │
└─────────┬───────┘ └─────────┬───────┘
│ │
└──────────┬───────────┘
┌─────────────────────┐
│ API Gateway │
│ (Traefik) │
└─────────┬───────────┘
┌───────────────┼───────────────┐
│ │ │
┌───▼───┐ ┌─────▼─────┐ ┌───▼───┐
│Billing│ │ Platform │ │Growth │
│Service│ │ Service │ │Service│
└───────┘ └───────────┘ └───────┘
```
**Key Principles:**
- Single responsibility per service
- Independent deployment
- Shared data through Cosmos DB
- JWT-based authentication via platform service
- Product-agnostic design (productId field)
### 2. Monorepo Structure
```
learning_voice_ai_agent/ # Product repo
├── src/ # Desktop app (Python)
├── backend/ # FastAPI backend
├── admin-dashboard-web/ # Next.js admin UI
├── user-dashboard-web/ # Next.js user portal
├── tracker-dashboard-web/ # Next.js tracker UI
└── mobile_app/ # Native mobile apps
learning_ai_common_plat/ # Shared platform
├── packages/ # @bytelyst/* libraries
│ ├── errors/ # Error types
│ ├── cosmos/ # DB client
│ ├── auth/ # JWT utils
│ ├── config/ # Config loader
│ ├── api-client/ # Fetch wrapper
│ └── design-tokens/ # Design system
└── services/ # @lysnrai/* microservices
├── billing-service/
├── growth-service/
├── platform-service/
└── tracker-service/
```
### 3. Mobile Architecture (MindLyst)
```
┌─────────────────────────────────────┐
│ KMP Shared Module │
│ (Business Logic, Repositories) │
└──────────────┬──────────────────────┘
┌──────────┴──────────┐
│ │
┌───▼────┐ ┌──────▼────┐
│ Android│ │ iOS │
│(Compose)│ │ (SwiftUI) │
└────────┘ └───────────┘
```
## Service Patterns
### Fastify Service Template
```typescript
// src/server.ts
import Fastify from 'fastify';
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox';
const server = Fastify({
logger: true,
}).withTypeProvider<TypeBoxTypeProvider>();
// Register plugins
await server.register(import('@fastify/cors'));
await server.register(import('./lib/auth'));
// Register modules
await server.register(import('./modules/auth/routes'), { prefix: '/api/auth' });
await server.register(import('./modules/users/routes'), { prefix: '/api/users' });
// Health check
server.get('/health', async (request, reply) => {
return {
status: 'ok',
service: 'billing-service',
requestId: request.id,
};
});
// Start server
try {
await server.listen({ port: 4002, host: '0.0.0.0' });
} catch (err) {
server.log.error(err);
process.exit(1);
}
```
### Module Pattern (types → repository → routes)
```typescript
// modules/auth/types.ts
import { Type } from '@sinclair/typebox';
export const LoginSchema = Type.Object({
email: Type.String({ format: 'email' }),
password: Type.String({ minLength: 8 }),
});
export const AuthResponseSchema = Type.Object({
token: Type.String(),
user: Type.Object({
id: Type.String(),
email: Type.String(),
}),
});
// modules/auth/repository.ts
export class AuthRepository {
constructor(private client: CosmosClient) {}
async validateUser(email: string, password: string): Promise<User | null> {
// Database logic
}
async createToken(userId: string): Promise<string> {
// JWT creation
}
}
// modules/auth/routes.ts
import { AuthRepository } from './repository';
import { LoginSchema, AuthResponseSchema } from './types';
export default async function authRoutes(server: FastifyInstance) {
const repo = new AuthRepository(server.cosmosClient);
server.post(
'/login',
{
schema: {
body: LoginSchema,
response: { 200: AuthResponseSchema },
},
},
async (request, reply) => {
const { email, password } = request.body;
const user = await repo.validateUser(email, password);
if (!user) {
reply.code(401);
return { error: 'Invalid credentials' };
}
const token = await repo.createToken(user.id);
return { token, user };
}
);
}
```
## Frontend Patterns
### Next.js Dashboard Structure
```
src/app/
├── (dashboard)/ # Layout group
│ ├── layout.tsx # Dashboard layout with sidebar
│ ├── page.tsx # Dashboard home
│ ├── users/
│ │ ├── page.tsx # Users list
│ │ └── [id]/
│ │ └── page.tsx # User detail
│ └── settings/
│ └── page.tsx # Settings page
├── api/ # API routes
│ ├── auth/
│ │ └── route.ts # /api/auth
│ └── users/
│ └── route.ts # /api/users
├── lib/ # Utilities
│ ├── cosmos.ts # @bytelyst/cosmos wrapper
│ ├── auth-server.ts # @bytelyst/auth wrapper
│ └── api-client.ts # Fetch wrapper
└── components/ # Reusable components
├── ui/ # Base UI components
└── forms/ # Form components
```
### Service Client Pattern
```typescript
// lib/billing-client.ts
import { createApiClient } from '@bytelyst/api-client';
const api = createApiClient({
baseUrl: process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003',
getToken: () => localStorage.getItem('token') || undefined,
});
export const billingClient = {
// Subscriptions
getSubscriptions: () => api.fetch<Subscription[]>('/api/subscriptions'),
createSubscription: (data: CreateSubscriptionDto) =>
api.fetch<Subscription>('/api/subscriptions', {
method: 'POST',
body: JSON.stringify(data),
}),
// Usage
getUsage: (userId: string) => api.fetch<Usage>(`/api/usage/${userId}`),
};
```
## Data Patterns
### Cosmos DB Document Structure
```typescript
// Base document interface
interface BaseDocument {
id: string;
productId: string; // REQUIRED for multi-tenancy
createdAt: string;
updatedAt: string;
_etag?: string;
_ts?: number;
}
// Example: User document
interface UserDocument extends BaseDocument {
email: string;
name: string;
role: 'admin' | 'user' | 'viewer';
permissions?: string[];
}
// Example: Tracker item document
interface TrackerItemDocument extends BaseDocument {
title: string;
description: string;
type: 'feature' | 'bug' | 'task';
status: 'planned' | 'in-progress' | 'completed';
visibility: 'public' | 'internal';
voteCount: number;
}
```
### Repository Pattern
```typescript
// repositories/base.ts
export abstract class BaseRepository<T extends BaseDocument> {
constructor(
protected client: CosmosClient,
protected database: string,
protected container: string
) {}
protected get container() {
return this.client.database(this.database).container(this.container);
}
async create(item: Omit<T, 'id' | 'createdAt' | 'updatedAt'>): Promise<T> {
const document: T = {
...item,
id: crypto.randomUUID(),
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
const { resource } = await this.container.items.create(document);
return resource!;
}
async getById(id: string, productId: string): Promise<T | null> {
const { resource } = await this.container.item(id, productId).read<T>();
return resource || null;
}
async update(id: string, productId: string, updates: Partial<T>): Promise<T> {
const { resource } = await this.container.item(id, productId).replace<T>({
...updates,
id,
productId,
updatedAt: new Date().toISOString(),
} as T);
return resource!;
}
}
```
## Authentication Patterns
### JWT Flow
```
┌─────────┐ Login ┌─────────────┐ Validate ┌─────────────┐
│ Frontend │───────▶│ Platform │────────────▶│ Other │
│ │ │ Service │ │ Services │
└─────────┘◀───────└─────────────┘ └─────────────┘
Token JWT Issuer JWT Validation
```
### Implementation
```typescript
// Platform service - JWT issuance
export async function loginRoute(server: FastifyInstance) {
server.post('/login', async (request, reply) => {
const { email, password } = request.body;
// Validate credentials
const user = await validateUser(email, password);
if (!user) return reply.code(401).send({ error: 'Invalid credentials' });
// Issue JWT
const token = await jwt.sign(
{ sub: user.id, email: user.email, role: user.role },
process.env.JWT_SECRET!,
{ expiresIn: '24h' }
);
return { token, user };
});
}
// Other services - JWT validation
export async function authMiddleware(server: FastifyInstance) {
server.addHook('onRequest', async (request, reply) => {
const token = request.headers.authorization?.replace('Bearer ', '');
if (!token) {
reply.code(401).send({ error: 'No token provided' });
return;
}
try {
const payload = await jwt.verify(token, process.env.JWT_SECRET!);
request.user = payload;
} catch {
reply.code(401).send({ error: 'Invalid token' });
}
});
}
```
## Configuration Patterns
### Environment-based Configuration
```typescript
// lib/config.ts
import { z } from 'zod';
const configSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
PORT: z.coerce.number().default(4002),
COSMOS_ENDPOINT: z.string(),
COSMOS_KEY: z.string(),
COSMOS_DATABASE: z.string().default('lysnrai'),
JWT_SECRET: z.string(),
AZURE_BLOB_CONNECTION_STRING: z.string().optional(),
});
export const config = configSchema.parse(process.env);
```
### Shared Package Configuration
```typescript
// @bytelyst/config
export function loadProductIdentity() {
const productId = process.env.PRODUCT_ID || 'lysnrai';
const env = process.env.NODE_ENV || 'development';
return {
productId,
env,
isProduction: env === 'production',
isDevelopment: env === 'development',
};
}
```
## Error Handling Patterns
### Standardized Error Types
```typescript
// @bytelyst/errors
export class BadRequestError extends Error {
constructor(
message: string,
public details?: any
) {
super(message);
this.name = 'BadRequestError';
}
}
export class UnauthorizedError extends Error {
constructor(message = 'Unauthorized') {
super(message);
this.name = 'UnauthorizedError';
}
}
export class NotFoundError extends Error {
constructor(resource: string) {
super(`${resource} not found`);
this.name = 'NotFoundError';
}
}
```
### Global Error Handler
```typescript
// Error handler for Fastify
server.setErrorHandler((error, request, reply) => {
request.log.error(error);
if (error instanceof BadRequestError) {
reply.code(400);
return { error: error.message, details: error.details };
}
if (error instanceof UnauthorizedError) {
reply.code(401);
return { error: error.message };
}
if (error instanceof NotFoundError) {
reply.code(404);
return { error: error.message };
}
// Default
reply.code(500);
return { error: 'Internal server error' };
});
```
## Notes
- **Consistency is key** - Follow established patterns
- **Product-agnostic design** - Always include productId
- **Shared packages first** - Don't duplicate code
- **Document decisions** - Use ADRs for major changes
- **Evolve patterns** - Improve but maintain consistency
## Related Skills
- [Local Development Setup](./local-development.md) - Running the architecture
- [Production Readiness](./production-readiness.md) - Validating the architecture
- [Debug Service](./debug-service.md) - Fixing architectural issues