Update README and documentation; refactor frontend components for improved structure and resilience
This commit is contained in:
474
services/api-docs/CLEAN_CODE.md
Normal file
474
services/api-docs/CLEAN_CODE.md
Normal file
@@ -0,0 +1,474 @@
|
||||
# API Documentation Service Clean Code Implementation
|
||||
|
||||
This document outlines the clean code principles and best practices implemented in the LabFusion API Documentation service (Node.js Express).
|
||||
|
||||
## 🏗️ **Architecture Overview**
|
||||
|
||||
The API Documentation service follows a modular architecture with clear separation of concerns:
|
||||
|
||||
```
|
||||
api-docs/
|
||||
├── server.js # Express application entry point
|
||||
├── package.json # Dependencies and scripts
|
||||
├── Dockerfile # Production container
|
||||
├── Dockerfile.dev # Development container
|
||||
└── README.md # Service documentation
|
||||
```
|
||||
|
||||
## 🧹 **Clean Code Principles Applied**
|
||||
|
||||
### **1. Single Responsibility Principle (SRP)**
|
||||
|
||||
#### **Service Purpose**
|
||||
- **Primary**: Aggregates OpenAPI specifications from all services
|
||||
- **Secondary**: Provides unified Swagger UI interface
|
||||
- **Tertiary**: Monitors service health and availability
|
||||
|
||||
#### **Function Responsibilities**
|
||||
- `fetchServiceSpec()`: Only fetches OpenAPI spec from a single service
|
||||
- `aggregateSpecs()`: Only combines multiple specs into one
|
||||
- `setupSwaggerUI()`: Only configures Swagger UI
|
||||
- `checkServiceHealth()`: Only monitors service health
|
||||
|
||||
### **2. Open/Closed Principle (OCP)**
|
||||
|
||||
#### **Extensible Service Configuration**
|
||||
```javascript
|
||||
// Easy to add new services without modifying existing code
|
||||
const services = [
|
||||
{ name: 'api-gateway', url: 'http://api-gateway:8080', specPath: '/v3/api-docs' },
|
||||
{ name: 'service-adapters', url: 'http://service-adapters:8000', specPath: '/openapi.json' },
|
||||
{ name: 'api-docs', url: 'http://api-docs:8083', specPath: '/openapi.json' }
|
||||
// New services can be added here
|
||||
];
|
||||
```
|
||||
|
||||
#### **Configurable Endpoints**
|
||||
```javascript
|
||||
// Service URLs can be configured via environment variables
|
||||
const API_GATEWAY_URL = process.env.API_GATEWAY_URL || 'http://api-gateway:8080';
|
||||
const SERVICE_ADAPTERS_URL = process.env.SERVICE_ADAPTERS_URL || 'http://service-adapters:8000';
|
||||
```
|
||||
|
||||
### **3. Dependency Inversion Principle (DIP)**
|
||||
|
||||
#### **Abstraction-Based Design**
|
||||
```javascript
|
||||
// Depends on abstractions, not concrete implementations
|
||||
const fetchServiceSpec = async (service) => {
|
||||
try {
|
||||
const response = await axios.get(`${service.url}${service.specPath}`, {
|
||||
timeout: 5000,
|
||||
headers: { 'Accept': 'application/json' }
|
||||
});
|
||||
return { success: true, data: response.data };
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### **Interface-Based Service Communication**
|
||||
- Uses HTTP client abstraction (axios)
|
||||
- Service health checking through standard endpoints
|
||||
- OpenAPI specification standard
|
||||
|
||||
### **4. Interface Segregation Principle (ISP)**
|
||||
|
||||
#### **Focused API Endpoints**
|
||||
- `/health`: Only health checking
|
||||
- `/services`: Only service status
|
||||
- `/openapi.json`: Only OpenAPI specification
|
||||
- `/`: Only Swagger UI interface
|
||||
|
||||
## 📝 **Code Quality Improvements**
|
||||
|
||||
### **1. Naming Conventions**
|
||||
|
||||
#### **Clear, Descriptive Names**
|
||||
```javascript
|
||||
// Good: Clear purpose
|
||||
const fetchServiceSpec = async (service) => { /* ... */ };
|
||||
const aggregateSpecs = (specs) => { /* ... */ };
|
||||
const checkServiceHealth = async (service) => { /* ... */ };
|
||||
|
||||
// Good: Descriptive variable names
|
||||
const serviceHealthStatus = await checkServiceHealth(service);
|
||||
const aggregatedSpec = aggregateSpecs(validSpecs);
|
||||
```
|
||||
|
||||
#### **Consistent Naming**
|
||||
- Functions: camelCase (e.g., `fetchServiceSpec`)
|
||||
- Variables: camelCase (e.g., `serviceHealthStatus`)
|
||||
- Constants: UPPER_SNAKE_CASE (e.g., `API_GATEWAY_URL`)
|
||||
- Objects: camelCase (e.g., `serviceConfig`)
|
||||
|
||||
### **2. Function Design**
|
||||
|
||||
#### **Small, Focused Functions**
|
||||
```javascript
|
||||
const fetchServiceSpec = async (service) => {
|
||||
try {
|
||||
const response = await axios.get(`${service.url}${service.specPath}`, {
|
||||
timeout: 5000,
|
||||
headers: { 'Accept': 'application/json' }
|
||||
});
|
||||
return { success: true, data: response.data };
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch spec from ${service.name}:`, error.message);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### **Single Level of Abstraction**
|
||||
- Each function handles one concern
|
||||
- Error handling separated from business logic
|
||||
- Clear return values
|
||||
|
||||
### **3. Error Handling**
|
||||
|
||||
#### **Consistent Error Responses**
|
||||
```javascript
|
||||
const handleError = (error, res) => {
|
||||
console.error('Error:', error);
|
||||
res.status(500).json({
|
||||
error: 'Internal server error',
|
||||
message: error.message,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
#### **Graceful Degradation**
|
||||
```javascript
|
||||
// If some services are unavailable, still serve available specs
|
||||
const validSpecs = specs.filter(spec => spec.success);
|
||||
if (validSpecs.length === 0) {
|
||||
return res.status(503).json({
|
||||
error: 'No services available',
|
||||
message: 'All services are currently unavailable'
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### **4. Configuration Management**
|
||||
|
||||
#### **Environment-Based Configuration**
|
||||
```javascript
|
||||
const config = {
|
||||
port: process.env.PORT || 8083,
|
||||
apiGatewayUrl: process.env.API_GATEWAY_URL || 'http://api-gateway:8080',
|
||||
serviceAdaptersUrl: process.env.SERVICE_ADAPTERS_URL || 'http://service-adapters:8000',
|
||||
apiDocsUrl: process.env.API_DOCS_URL || 'http://api-docs:8083'
|
||||
};
|
||||
```
|
||||
|
||||
#### **Centralized Configuration**
|
||||
- All configuration in one place
|
||||
- Environment variable support
|
||||
- Default values for development
|
||||
|
||||
## 🔧 **Express.js Best Practices**
|
||||
|
||||
### **1. Middleware Usage**
|
||||
|
||||
#### **Appropriate Middleware**
|
||||
```javascript
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(express.static('public'));
|
||||
|
||||
// Error handling middleware
|
||||
app.use((error, req, res, next) => {
|
||||
handleError(error, res);
|
||||
});
|
||||
```
|
||||
|
||||
#### **CORS Configuration**
|
||||
```javascript
|
||||
app.use(cors({
|
||||
origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
|
||||
credentials: true
|
||||
}));
|
||||
```
|
||||
|
||||
### **2. Route Organization**
|
||||
|
||||
#### **Clear Route Structure**
|
||||
```javascript
|
||||
// Health check endpoint
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({
|
||||
status: 'healthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
uptime: process.uptime()
|
||||
});
|
||||
});
|
||||
|
||||
// Service status endpoint
|
||||
app.get('/services', async (req, res) => {
|
||||
try {
|
||||
const serviceStatuses = await Promise.allSettled(
|
||||
services.map(service => checkServiceHealth(service))
|
||||
);
|
||||
// Process and return statuses
|
||||
} catch (error) {
|
||||
handleError(error, res);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### **RESTful Endpoints**
|
||||
- GET `/health`: Health check
|
||||
- GET `/services`: Service status
|
||||
- GET `/openapi.json`: OpenAPI specification
|
||||
- GET `/`: Swagger UI
|
||||
|
||||
### **3. Async/Await Usage**
|
||||
|
||||
#### **Proper Async Handling**
|
||||
```javascript
|
||||
app.get('/openapi.json', async (req, res) => {
|
||||
try {
|
||||
const specs = await Promise.allSettled(
|
||||
services.map(service => fetchServiceSpec(service))
|
||||
);
|
||||
|
||||
const validSpecs = specs
|
||||
.filter(result => result.status === 'fulfilled' && result.value.success)
|
||||
.map(result => result.value.data);
|
||||
|
||||
if (validSpecs.length === 0) {
|
||||
return res.status(503).json({
|
||||
error: 'No services available',
|
||||
message: 'All services are currently unavailable'
|
||||
});
|
||||
}
|
||||
|
||||
const aggregatedSpec = aggregateSpecs(validSpecs);
|
||||
res.json(aggregatedSpec);
|
||||
} catch (error) {
|
||||
handleError(error, res);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## 📊 **Data Processing Best Practices**
|
||||
|
||||
### **1. OpenAPI Specification Aggregation**
|
||||
|
||||
#### **Clean Spec Processing**
|
||||
```javascript
|
||||
const aggregateSpecs = (specs) => {
|
||||
const aggregated = {
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
title: 'LabFusion API',
|
||||
description: 'Unified API documentation for all LabFusion services',
|
||||
version: '1.0.0'
|
||||
},
|
||||
servers: [],
|
||||
paths: {},
|
||||
components: {
|
||||
schemas: {},
|
||||
securitySchemes: {}
|
||||
}
|
||||
};
|
||||
|
||||
specs.forEach(spec => {
|
||||
// Merge paths with service prefix
|
||||
Object.entries(spec.paths || {}).forEach(([path, methods]) => {
|
||||
const prefixedPath = `/${spec.info?.title?.toLowerCase().replace(/\s+/g, '-')}${path}`;
|
||||
aggregated.paths[prefixedPath] = methods;
|
||||
});
|
||||
|
||||
// Merge components
|
||||
if (spec.components?.schemas) {
|
||||
Object.assign(aggregated.components.schemas, spec.components.schemas);
|
||||
}
|
||||
});
|
||||
|
||||
return aggregated;
|
||||
};
|
||||
```
|
||||
|
||||
### **2. Service Health Monitoring**
|
||||
|
||||
#### **Robust Health Checking**
|
||||
```javascript
|
||||
const checkServiceHealth = async (service) => {
|
||||
try {
|
||||
const response = await axios.get(`${service.url}/health`, {
|
||||
timeout: 5000,
|
||||
headers: { 'Accept': 'application/json' }
|
||||
});
|
||||
|
||||
return {
|
||||
name: service.name,
|
||||
status: 'healthy',
|
||||
responseTime: response.headers['x-response-time'] || 'unknown',
|
||||
lastCheck: new Date().toISOString()
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
name: service.name,
|
||||
status: 'unhealthy',
|
||||
error: error.message,
|
||||
lastCheck: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## 🚀 **Performance Optimizations**
|
||||
|
||||
### **1. Caching Strategy**
|
||||
```javascript
|
||||
// Simple in-memory caching for OpenAPI specs
|
||||
let cachedSpec = null;
|
||||
let lastFetch = 0;
|
||||
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
app.get('/openapi.json', async (req, res) => {
|
||||
const now = Date.now();
|
||||
|
||||
if (cachedSpec && (now - lastFetch) < CACHE_DURATION) {
|
||||
return res.json(cachedSpec);
|
||||
}
|
||||
|
||||
// Fetch fresh spec
|
||||
const spec = await fetchAndAggregateSpecs();
|
||||
cachedSpec = spec;
|
||||
lastFetch = now;
|
||||
|
||||
res.json(spec);
|
||||
});
|
||||
```
|
||||
|
||||
### **2. Error Handling**
|
||||
```javascript
|
||||
// Global error handler
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
||||
});
|
||||
|
||||
process.on('uncaughtException', (error) => {
|
||||
console.error('Uncaught Exception:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
```
|
||||
|
||||
### **3. Resource Management**
|
||||
```javascript
|
||||
// Graceful shutdown
|
||||
process.on('SIGTERM', () => {
|
||||
console.log('SIGTERM received, shutting down gracefully');
|
||||
server.close(() => {
|
||||
console.log('Process terminated');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## 🧪 **Testing Strategy**
|
||||
|
||||
### **1. Unit Testing**
|
||||
```javascript
|
||||
// tests/server.test.js
|
||||
const request = require('supertest');
|
||||
const app = require('../server');
|
||||
|
||||
describe('API Documentation Service', () => {
|
||||
test('GET /health should return healthy status', async () => {
|
||||
const response = await request(app).get('/health');
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.status).toBe('healthy');
|
||||
});
|
||||
|
||||
test('GET /services should return service statuses', async () => {
|
||||
const response = await request(app).get('/services');
|
||||
expect(response.status).toBe(200);
|
||||
expect(Array.isArray(response.body)).toBe(true);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### **2. Integration Testing**
|
||||
```javascript
|
||||
test('GET /openapi.json should return aggregated spec', async () => {
|
||||
const response = await request(app).get('/openapi.json');
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.openapi).toBe('3.0.0');
|
||||
expect(response.body.info.title).toBe('LabFusion API');
|
||||
});
|
||||
```
|
||||
|
||||
## 📋 **Code Review Checklist**
|
||||
|
||||
### **Function Design**
|
||||
- [ ] Single responsibility per function
|
||||
- [ ] Clear function names
|
||||
- [ ] Proper error handling
|
||||
- [ ] Consistent return values
|
||||
- [ ] Async/await usage
|
||||
|
||||
### **Error Handling**
|
||||
- [ ] Try-catch blocks where needed
|
||||
- [ ] Proper HTTP status codes
|
||||
- [ ] User-friendly error messages
|
||||
- [ ] Logging for debugging
|
||||
|
||||
### **Configuration**
|
||||
- [ ] Environment variable support
|
||||
- [ ] Default values
|
||||
- [ ] Centralized configuration
|
||||
- [ ] Service-specific settings
|
||||
|
||||
### **Performance**
|
||||
- [ ] Caching where appropriate
|
||||
- [ ] Timeout handling
|
||||
- [ ] Resource cleanup
|
||||
- [ ] Memory management
|
||||
|
||||
## 🎯 **Benefits Achieved**
|
||||
|
||||
### **1. Maintainability**
|
||||
- Clear separation of concerns
|
||||
- Easy to modify and extend
|
||||
- Consistent patterns throughout
|
||||
- Self-documenting code
|
||||
|
||||
### **2. Testability**
|
||||
- Isolated functions
|
||||
- Mockable dependencies
|
||||
- Clear interfaces
|
||||
- Testable business logic
|
||||
|
||||
### **3. Performance**
|
||||
- Caching for frequently accessed data
|
||||
- Efficient error handling
|
||||
- Resource management
|
||||
- Graceful degradation
|
||||
|
||||
### **4. Scalability**
|
||||
- Modular architecture
|
||||
- Easy to add new services
|
||||
- Horizontal scaling support
|
||||
- Configuration-driven services
|
||||
|
||||
## 🔮 **Future Improvements**
|
||||
|
||||
### **Potential Enhancements**
|
||||
1. **Redis Caching**: Distributed caching for multiple instances
|
||||
2. **Metrics**: Prometheus metrics integration
|
||||
3. **Health Checks**: More comprehensive health monitoring
|
||||
4. **Rate Limiting**: API rate limiting
|
||||
5. **Authentication**: Service authentication
|
||||
|
||||
### **Monitoring & Observability**
|
||||
1. **Logging**: Structured logging with correlation IDs
|
||||
2. **Tracing**: Distributed tracing
|
||||
3. **Metrics**: Service performance metrics
|
||||
4. **Alerts**: Service availability alerts
|
||||
|
||||
This clean code implementation makes the API Documentation service more maintainable, testable, and scalable while following Express.js and Node.js best practices.
|
||||
Reference in New Issue
Block a user