Skip to main content

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:

  1. Metadata Anti-Patterns
  2. Security Anti-Patterns
  3. Performance Anti-Patterns
  4. Architecture Anti-Patterns
  5. 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

  1. Hardcoding field names → Use metadata-driven access
  2. Bypassing validation → Validate against TB_VAR
  3. Ignoring COD_ON_OFF → Respect field visibility
  4. Missing context → Always include full context chain
  5. Insecure tokens → Use httpOnly cookies or secure storage
  6. No grant checking → Verify permissions first
  7. N+1 queries → Use expansion or batch requests
  8. No pagination → Always paginate large datasets
  9. No caching → Implement caching for frequently accessed data
  10. Business logic in frontend → Centralize in backend
  11. Direct TREC manipulation → Use lifecycle operations
  12. Skipping OUTBOX → Leverage event sourcing
  13. Wrong context values → Use correct organizational unit
  14. 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