Anti-Patterns
Overview
This guide identifies common mistakes when working with Q01 Core APIs and provides corrected implementations. Learning what NOT to do is as important as learning best practices.
Categories:
- Metadata Anti-Patterns
- Security Anti-Patterns
- Performance Anti-Patterns
- Architecture Anti-Patterns
- Data Management Anti-Patterns
Metadata Anti-Patterns
❌ Anti-Pattern 1: Hardcoding Field Names
Problem: Hardcoded field names break when metadata changes.
// ❌ BAD - Hardcoded field names
async function getProducts() {
const products = await api.get('/api/v4/core/products');
return products.data.map(p => ({
id: p.PRD_ID,
name: p.XPRD01, // Hardcoded
price: p.XPRD02, // Hardcoded
code: p.XPRD03, // Hardcoded
category: p.XPRD05 // Hardcoded
}));
}
Why it's bad:
- Breaks when field names change in TB_VAR
- No field visibility checking (COD_ON_OFF)
- Tight coupling to specific dimension
✅ CORRECT - Metadata-driven field access:
async function getProducts() {
// 1. Fetch field metadata
const fields = await api.get('/api/v4/core/products/fields');
// 2. Filter visible fields (COD_ON_OFF L flag)
const visibleFields = fields.data
.filter(f => f.COD_ON_OFF.includes('L'))
.map(f => f.VAR);
// 3. Fetch products with only visible fields
const products = await api.get('/api/v4/core/products', {
params: {fields: visibleFields.join(',')}
});
// 4. Dynamic mapping
return products.data.map(p => {
const mapped = {};
for (const field of visibleFields) {
mapped[field] = p[field];
}
return mapped;
});
}
❌ Anti-Pattern 2: Bypassing Metadata Validation
Problem: Skipping TB_VAR validation leads to data inconsistencies.
// ❌ BAD - No validation
async function createProduct(data) {
return api.post('/api/v4/core/products', data);
}
// Usage
await createProduct({
XPRD01: null, // Should be required!
XPRD02: 'invalid', // Should be numeric!
XPRD99: 'unknown' // Field doesn't exist!
});
✅ CORRECT - Validate against TB_VAR:
async function createProduct(data) {
// 1. Fetch field metadata
const fields = await api.get('/api/v4/core/products/fields');
// 2. Validate required fields
for (const field of fields.data) {
if (field.REQUIRED === 'Y' && !data[field.VAR]) {
throw new Error(`Required field missing: ${field.VAR}`);
}
}
// 3. Validate field types
for (const [key, value] of Object.entries(data)) {
const fieldMeta = fields.data.find(f => f.VAR === key);
if (!fieldMeta) {
throw new Error(`Unknown field: ${key}`);
}
if (fieldMeta.TYPE === 'numeric' && typeof value !== 'number') {
throw new Error(`Invalid type for ${key}: expected number`);
}
}
// 4. Create product
return api.post('/api/v4/core/products', data);
}
❌ Anti-Pattern 3: Ignoring COD_ON_OFF Flags
Problem: Showing fields that should be hidden.
// ❌ BAD - Ignoring field visibility
async function renderProductForm() {
const fields = await api.get('/api/v4/core/products/fields');
// Shows ALL fields, including hidden ones!
return fields.data.map(field => (
<input name={field.VAR} label={field.LABEL} />
));
}
✅ CORRECT - Respect COD_ON_OFF:
async function renderProductForm() {
const fields = await api.get('/api/v4/core/products/fields');
// Filter by COD_ON_OFF N flag (visible in new form)
const newFormFields = fields.data.filter(field =>
field.COD_ON_OFF.includes('N')
);
return newFormFields.map(field => (
<input
name={field.VAR}
label={field.LABEL}
required={field.REQUIRED === 'Y'}
type={getInputType(field.TYPE)}
/>
));
}
Security Anti-Patterns
❌ Anti-Pattern 4: Missing Context Chain
Problem: Requests without context bypass security filters.
// ❌ BAD - No context
async function getProducts() {
return api.get('/api/v4/core/products');
}
Why it's bad:
- No tenant isolation (source)
- No permission filtering (peso)
- No environment filtering (ambiente)
- No organizational filtering (centro_dett)
✅ CORRECT - Always include context:
class CoreAPIClient {
constructor(config) {
this.baseURL = config.baseURL;
this.context = config.context;
// Add interceptor to include context headers
this.axios = axios.create({baseURL: this.baseURL});
this.axios.interceptors.request.use(config => {
config.headers['X-Source'] = this.context.source;
config.headers['X-Peso'] = this.context.peso;
config.headers['X-Ambiente'] = this.context.ambiente;
config.headers['X-Centro-Dett'] = this.context.centro_dett;
config.headers['X-Modulo'] = this.context.modulo;
config.headers['X-Lingua'] = this.context.lingua;
return config;
});
}
async get(url, options) {
return this.axios.get(url, options);
}
}
// Usage
const client = new CoreAPIClient({
baseURL: 'https://api.q01.io',
context: {
source: 'tenant_123',
peso: 3,
ambiente: 'P',
centro_dett: 'HQ',
modulo: 'sales',
lingua: 'IT'
}
});
❌ Anti-Pattern 5: Storing Tokens Insecurely
Problem: Tokens exposed in localStorage or code.
// ❌ BAD - Insecure storage
localStorage.setItem('token', accessToken); // XSS vulnerable!
// ❌ BAD - Hardcoded token
const TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
✅ CORRECT - Secure token storage:
// ✅ GOOD - httpOnly cookie (server-side)
res.cookie('accessToken', token, {
httpOnly: true, // Not accessible via JavaScript
secure: true, // HTTPS only
sameSite: 'strict', // CSRF protection
maxAge: 900000 // 15 minutes
});
// ✅ GOOD - Environment variable for server-side
const TOKEN = process.env.CORE_API_TOKEN;
// ✅ GOOD - sessionStorage for short-lived tokens (if cookies not possible)
sessionStorage.setItem('token', accessToken); // Better than localStorage
❌ Anti-Pattern 6: No Grant Checking
Problem: Performing operations without checking permissions.
// ❌ BAD - No grant check
async function deleteProduct(id) {
return api.delete(`/api/v4/core/products/${id}`);
}
✅ CORRECT - Check grants first:
async function deleteProduct(id) {
// 1. Check if user has delete grant
const grants = await api.get('/api/v4/core/menu');
const hasDeleteGrant = grants.data.some(g =>
g.MENU_CODE === 'PRD' && g.GRANTS.includes('D')
);
if (!hasDeleteGrant) {
throw new Error('Permission denied: DELETE grant required');
}
// 2. Perform delete
return api.delete(`/api/v4/core/products/${id}`);
}
Performance Anti-Patterns
❌ Anti-Pattern 7: N+1 Query Problem
Problem: Making separate requests for related data.
// ❌ BAD - N+1 queries
async function getProductsWithCategories() {
// 1 query
const products = await api.get('/api/v4/core/products?limit=100');
// 100 queries!
for (const product of products.data) {
product.category = await api.get(`/api/v4/core/categories/${product.XPRD05}`);
}
return products;
}
// Total: 101 queries!
✅ CORRECT - Use expansion or batch requests:
// ✅ GOOD - Use expand parameter
async function getProductsWithCategories() {
// Single query with expansion
const products = await api.get('/api/v4/core/products', {
params: {
limit: 100,
expand: 'XPRD05' // Expand category field
}
});
return products;
}
// ✅ ALTERNATIVE - Batch request
async function getProductsWithCategories() {
// 1st query
const products = await api.get('/api/v4/core/products?limit=100');
// 2nd query (single request for all categories)
const categoryIds = [...new Set(products.data.map(p => p.XPRD05))];
const categories = await api.get('/api/v4/core/categories', {
params: {filters: `CAT_ID:in:${categoryIds.join(',')}`}
});
// Map in memory
const categoryMap = Object.fromEntries(
categories.data.map(c => [c.CAT_ID, c])
);
products.data.forEach(p => {
p.category = categoryMap[p.XPRD05];
});
return products;
}
// Total: 2 queries!
❌ Anti-Pattern 8: Not Using Pagination
Problem: Fetching all records at once causes memory issues.
// ❌ BAD - No pagination
async function getAllProducts() {
return api.get('/api/v4/core/products?limit=999999'); // Disaster!
}
✅ CORRECT - Always paginate:
// ✅ GOOD - Paginated fetching
async function* fetchAllProducts(batchSize = 100) {
let offset = 0;
let hasMore = true;
while (hasMore) {
const response = await api.get('/api/v4/core/products', {
params: {limit: batchSize, offset: offset}
});
yield response.data;
hasMore = response.data.length === batchSize;
offset += batchSize;
}
}
// Usage
for await (const batch of fetchAllProducts(100)) {
processBatch(batch);
}
❌ Anti-Pattern 9: No Caching
Problem: Fetching same data repeatedly.
// ❌ BAD - No caching
async function getProduct(id) {
return api.get(`/api/v4/core/products/${id}`);
}
// Called 100 times = 100 API requests!
for (let i = 0; i < 100; i++) {
await getProduct(123);
}
✅ CORRECT - Implement caching:
class CachedAPIClient {
constructor(api, ttl = 300000) {
this.api = api;
this.cache = new Map();
this.ttl = ttl;
}
async get(url) {
const cached = this.cache.get(url);
if (cached && Date.now() - cached.timestamp < this.ttl) {
return cached.data;
}
const data = await this.api.get(url);
this.cache.set(url, {
data: data,
timestamp: Date.now()
});
return data;
}
invalidate(url) {
this.cache.delete(url);
}
}
// Usage
const cachedAPI = new CachedAPIClient(api);
// Called 100 times = 1 API request + 99 cache hits!
for (let i = 0; i < 100; i++) {
await cachedAPI.get('/api/v4/core/products/123');
}
Architecture Anti-Patterns
❌ Anti-Pattern 10: Business Logic in Frontend
Problem: Duplicating business logic across clients.
// ❌ BAD - Business logic in frontend
function calculateOrderTotal(items) {
let total = 0;
for (const item of items) {
let price = item.price;
// Discount logic (duplicated in every client!)
if (item.quantity >= 10) {
price *= 0.9; // 10% discount
}
if (item.category === 'PROMO') {
price *= 0.8; // 20% discount
}
total += price * item.quantity;
}
return total;
}
Why it's bad:
- Logic duplicated across web, mobile, backend
- Inconsistent behavior when logic changes
- Hard to maintain
✅ CORRECT - Business logic in backend:
// ✅ GOOD - Business logic on server
// Frontend just calls API
async function calculateOrderTotal(items) {
const response = await api.post('/api/v4/core/orders/calculate', {
items: items
});
return response.total;
}
// Backend implements consistent logic
// POST /api/v4/core/orders/calculate
async function calculateTotal(req, res) {
const {items} = req.body;
let total = 0;
for (const item of items) {
const product = await getProduct(item.productId);
let price = product.price;
// Centralized discount logic
price = applyDiscounts(price, item.quantity, product.category);
total += price * item.quantity;
}
res.json({total});
}
❌ Anti-Pattern 11: Direct TREC Manipulation
Problem: Manually setting TREC instead of using lifecycle operations.
// ❌ BAD - Direct TREC manipulation
await api.patch(`/api/v4/core/products/${id}`, {
TREC: 'C' // Manually setting to cancelled
});
Why it's bad:
- Bypasses lifecycle validation
- No OUTBOX event generated
- Cascade operations not triggered
✅ CORRECT - Use proper lifecycle operations:
// ✅ GOOD - Use DELETE for cancellation
await api.delete(`/api/v4/core/products/${id}`);
// This properly:
// 1. Changes TREC from N to C
// 2. Sets DELETED_BY and DELETED_AT
// 3. Generates OUTBOX event (ProductDeleted)
// 4. Triggers cascade operations (if configured)
❌ Anti-Pattern 12: Skipping OUTBOX Pattern
Problem: Not leveraging event sourcing for consistency.
// ❌ BAD - Manual synchronization
async function createProduct(data) {
// 1. Create in Core API
const product = await coreAPI.post('/api/v4/core/products', data);
// 2. Manual sync to search (can fail!)
await elasticsearch.index({
index: 'products',
id: product.PRD_ID,
body: product
});
// 3. Manual cache update (can fail!)
await redis.set(`product:${product.PRD_ID}`, JSON.stringify(product));
return product;
}
Problems:
- Search/cache update can fail silently
- No retry mechanism
- Data inconsistency
✅ CORRECT - Use OUTBOX subscribers:
// ✅ GOOD - Let OUTBOX handle sync
async function createProduct(data) {
// Just create product - OUTBOX handles the rest
const product = await coreAPI.post('/api/v4/core/products', data);
// OUTBOX automatically:
// 1. Generates ProductCreated event
// 2. Publishes to RabbitMQ
// 3. search-indexer updates Elasticsearch
// 4. cache-invalidator clears Redis
// All with retry and error handling!
return product;
}
Data Management Anti-Patterns
❌ Anti-Pattern 13: Mixing center_dett Values
Problem: Using wrong organizational unit context.
// ❌ BAD - Wrong center_dett
async function getHeadquarterOrders() {
// User is in Milan branch but queries HQ data
const orders = await api.get('/api/v4/core/orders', {
headers: {
'X-Centro-Dett': 'HQ' // Wrong! User is in MILAN
}
});
}
✅ CORRECT - Use correct context:
// ✅ GOOD - Correct center_dett
async function getUserOrders(userCentroDett) {
const orders = await api.get('/api/v4/core/orders', {
headers: {
'X-Centro-Dett': userCentroDett // User's actual organizational unit
}
});
}
❌ Anti-Pattern 14: Not Validating Input
Problem: Accepting user input without validation.
// ❌ BAD - No validation
async function createProduct(req, res) {
const data = req.body; // Direct user input!
return api.post('/api/v4/core/products', data);
}
Risks:
- SQL injection (if metadata has vulnerabilities)
- XSS attacks
- Data corruption
- Invalid data types
✅ CORRECT - Always validate:
// ✅ GOOD - Validate and sanitize
async function createProduct(req, res) {
const data = req.body;
// 1. Validate required fields
if (!data.XPRD01 || !data.XPRD02) {
return res.status(400).json({error: 'Missing required fields'});
}
// 2. Validate types
if (typeof data.XPRD02 !== 'number' || data.XPRD02 <= 0) {
return res.status(400).json({error: 'Invalid price'});
}
// 3. Sanitize text fields
data.XPRD01 = sanitizeHTML(data.XPRD01);
// 4. Create product
return api.post('/api/v4/core/products', data);
}
Summary
Critical Anti-Patterns to Avoid
- ❌ Hardcoding field names → Use metadata-driven access
- ❌ Bypassing validation → Validate against TB_VAR
- ❌ Ignoring COD_ON_OFF → Respect field visibility
- ❌ Missing context → Always include full context chain
- ❌ Insecure tokens → Use httpOnly cookies or secure storage
- ❌ No grant checking → Verify permissions first
- ❌ N+1 queries → Use expansion or batch requests
- ❌ No pagination → Always paginate large datasets
- ❌ No caching → Implement caching for frequently accessed data
- ❌ Business logic in frontend → Centralize in backend
- ❌ Direct TREC manipulation → Use lifecycle operations
- ❌ Skipping OUTBOX → Leverage event sourcing
- ❌ Wrong context values → Use correct organizational unit
- ❌ No input validation → Always validate and sanitize
Quick Reference
Always DO:
- ✅ Use metadata-driven field access
- ✅ Include full context chain
- ✅ Check grants before operations
- ✅ Respect COD_ON_OFF flags
- ✅ Validate input data
- ✅ Use pagination
- ✅ Implement caching
- ✅ Leverage OUTBOX pattern
Never DO:
- ❌ Hardcode field names
- ❌ Skip validation
- ❌ Bypass security checks
- ❌ Manipulate TREC directly
- ❌ Store tokens insecurely
- ❌ Fetch all data without pagination
- ❌ Duplicate business logic
- ❌ Make N+1 queries
Related Concepts
- Use Cases - Correct implementation patterns
- Security Tips - Security best practices
- Performance Tips - Optimization strategies
- Best Practices - Overview