Update Operations (PUT)
Overview
PUT operations perform full record updates, replacing ALL field values. CoreService routes PUT requests to CoreWrite, which validates, updates TREC state, executes cascades, and publishes events.
Endpoint:
PUT /api/v4/core/{dim}/{dim_id}
Content-Type: application/json
Authorization: Bearer {token}
Behavior:
- Sets TREC='M' (Modified)
- Sets LDATA, LOWNER (last update audit fields)
- Replaces ALL fields in request body
- Validates COD_ON_OFF='M' permissions
- Executes UPDATE_CASCADE operations
- Publishes update event to outbox
⚠️ Warning: PUT replaces entire record. Use PATCH for partial updates.
Basic PUT Request
Full Record Update
Request:
PUT /api/v4/core/PRD/123
Authorization: Bearer eyJhbGc...
Content-Type: application/json
{
"data": {
"XPRD01": "Widget Pro Updated",
"XPRD02": 109.99,
"XPRD04": "Updated description",
"XPRD05": "cat_electronics",
"XPRD06": true,
"XPRD09": 150
}
}
Response:
{
"status": "success",
"data": {
"PRD_ID": 123,
"XPRD01": "Widget Pro Updated",
"XPRD02": 109.99,
"XPRD03": "PRD-2025-001", // Unchanged
"XPRD04": "Updated description",
"XPRD05": "cat_electronics",
"XPRD06": true,
"XPRD09": 150,
"TREC": "M", // Changed from 'N' to 'M'
"CDATA": "20251219153045", // Original creation time
"OWNER": "user@example.com", // Original creator
"LDATA": "20251219160000", // Last update timestamp
"LOWNER": "admin@example.com" // Last updater
}
}
TREC State Transition
Lifecycle Changes
POST → PUT Transition:
TREC='N' (New)
↓ PUT request
TREC='M' (Modified)
Database Update:
-- Before PUT
SELECT TREC, CDATA, OWNER, LDATA, LOWNER
FROM TB_ANAG_PRD00
WHERE PRD_ID = 123;
-- Result: 'N', '20251219153045', 'user@example.com', NULL, NULL
-- After PUT
SELECT TREC, CDATA, OWNER, LDATA, LOWNER
FROM TB_ANAG_PRD00
WHERE PRD_ID = 123;
-- Result: 'M', '20251219153045', 'user@example.com', '20251219160000', 'admin@example.com'
Audit Trail:
- CDATA/OWNER: Preserved (original creation info)
- LDATA/LOWNER: Updated (last modification info)
- TREC: Changed to 'M'
PUT vs PATCH
Key Differences
| Aspect | PUT | PATCH |
|---|---|---|
| Fields | Replaces ALL fields | Updates ONLY specified fields |
| Missing fields | Set to NULL/default | Unchanged |
| Use case | Full record replacement | Partial field updates |
| Safety | ⚠️ Can lose data | ✅ Safe for single field updates |
PUT Overwrites Fields
Example:
Original record:
{
"PRD_ID": 123,
"XPRD01": "Widget Pro",
"XPRD02": 99.99,
"XPRD04": "Original description",
"XPRD05": "cat_electronics",
"XPRD06": true,
"XPRD09": 100
}
PUT request (missing XPRD04):
PUT /api/v4/core/PRD/123
{
"data": {
"XPRD01": "Widget Pro",
"XPRD02": 109.99,
"XPRD05": "cat_electronics",
"XPRD06": true,
"XPRD09": 150
// Missing XPRD04
}
}
Result:
{
"PRD_ID": 123,
"XPRD01": "Widget Pro",
"XPRD02": 109.99,
"XPRD04": null, // ⚠️ CLEARED because not in request
"XPRD05": "cat_electronics",
"XPRD06": true,
"XPRD09": 150,
"TREC": "M"
}
⚠️ XPRD04 was cleared! Use PATCH to avoid this.
COD_ON_OFF Validation
Modify Permission
TB_COST.COD_ON_OFF must include 'M' flag for PUT:
-- XPRD01 can be updated (M flag present)
INSERT INTO TB_COST (COD_DIM, NUM_COST, COD_VAR, COD_ON_OFF) VALUES
('PRD', 1, 'XPRD01', 'LDNMR');
-- XPRD03 cannot be updated (M flag missing)
INSERT INTO TB_COST (COD_DIM, NUM_COST, COD_VAR, COD_ON_OFF) VALUES
('PRD', 3, 'XPRD03', 'LDR'); -- No 'M' flag (counter, read-only)
Validation Error:
Request:
PUT /api/v4/core/PRD/123
{
"data": {
"XPRD01": "Product",
"XPRD03": "PRD-2025-999" // Read-only counter
}
}
Response:
{
"error": "ValidationError",
"message": "Field not modifiable: XPRD03",
"code": "FIELD_NOT_MODIFIABLE",
"field": "XPRD03",
"dimension": "PRD"
}
UPDATE_CASCADE Operations
Propagating Updates
TB_COST.UPDATE_CASCADE triggers related record updates:
-- When product price changes, update all order items
INSERT INTO TB_COST (COD_DIM, NUM_COST, COD_VAR, UPDATE_CASCADE) VALUES
('PRD', 2, 'XPRD02', 'ORDITEM:XORDITEM09');
Cascade Behavior:
Request:
PUT /api/v4/core/PRD/123
{
"data": {
"XPRD01": "Widget Pro",
"XPRD02": 89.99 // Price changed from 99.99
}
}
Result:
-- Updates product
UPDATE TB_ANAG_PRD00
SET XPRD02 = 89.99, TREC = 'M', LDATA = '20251219160000'
WHERE PRD_ID = 123;
-- Cascades to order items (if configured)
UPDATE TB_ANAG_ORDITEM00
SET XORDITEM09 = 89.99, TREC = 'M', LDATA = '20251219160000'
WHERE XORDITEM10 = 123;
⚠️ Cascade caution: Use cascades carefully. Updating product price may NOT want to change historical orders.
Outbox Event
Update Event Publication
Every PUT publishes event to RabbitMQ:
Outbox Record:
{
"event_id": "evt_660f9511",
"event_type": "ProductUpdated",
"aggregate_id": "123",
"aggregate_type": "PRD",
"operation": "PUT",
"timestamp": "2025-12-19T16:00:00Z",
"payload": {
"PRD_ID": 123,
"XPRD01": "Widget Pro Updated",
"XPRD02": 109.99,
"TREC": "M"
},
"previous_payload": {
"XPRD01": "Widget Pro",
"XPRD02": 99.99
},
"user": "admin@example.com",
"session": {
"source": "productManagement",
"centro_dett": "admin"
}
}
RabbitMQ Exchange:
Exchange: corewrite.fanout
Routing Key: product.updated
Subscribers: search-indexer, cache-invalidator, price-change-notifier
JavaScript Implementation
Product Update Service
class ProductService {
constructor(apiBase, token) {
this.apiBase = apiBase;
this.token = token;
}
/**
* Full record update (PUT)
*/
async updateProduct(productId, productData) {
const response = await fetch(`${this.apiBase}/api/v4/core/PRD/${productId}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${this.token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ data: productData })
});
if (!response.ok) {
const error = await response.json();
throw new ProductUpdateError(error);
}
return response.json();
}
/**
* Safe update - fetches current, merges changes, then PUTs
*/
async safeUpdate(productId, changes) {
// 1. Fetch current record
const current = await this.getProduct(productId);
// 2. Merge changes
const updated = { ...current.data, ...changes };
// 3. PUT full record
const result = await this.updateProduct(productId, updated);
console.log('Product updated:', result.data.PRD_ID);
console.log('TREC changed to:', result.data.TREC);
return result;
}
/**
* Get product by ID
*/
async getProduct(productId) {
const response = await fetch(`${this.apiBase}/api/v4/core/PRD/${productId}`, {
headers: { 'Authorization': `Bearer ${this.token}` }
});
return response.json();
}
}
class ProductUpdateError extends Error {
constructor(errorResponse) {
super(errorResponse.message);
this.code = errorResponse.code;
this.field = errorResponse.field;
}
}
// Usage
const productService = new ProductService(apiBase, token);
// ❌ Unsafe - might lose fields
await productService.updateProduct(123, {
XPRD01: 'Updated Name',
XPRD02: 109.99
// Missing other fields!
});
// ✅ Safe - merges with current data
await productService.safeUpdate(123, {
XPRD02: 109.99 // Only change price
});
Update with Optimistic Locking
class ProductService {
/**
* Update with version check (optimistic locking)
*/
async updateWithVersionCheck(productId, changes, expectedVersion) {
// 1. Fetch current record
const current = await this.getProduct(productId);
// 2. Check version
if (current.data.LDATA !== expectedVersion) {
throw new Error('Record was modified by another user. Please refresh and try again.');
}
// 3. Merge and update
const updated = { ...current.data, ...changes };
return this.updateProduct(productId, updated);
}
}
// Usage
const product = await productService.getProduct(123);
const version = product.data.LDATA; // "20251219153045"
// ... user makes changes ...
try {
await productService.updateWithVersionCheck(123, {
XPRD02: 109.99
}, version);
} catch (error) {
alert('Record was modified by another user');
}
Common Patterns
Pattern 1: Read-Modify-Write
Safe pattern for full updates:
async function updateProductPrice(productId, newPrice) {
// 1. Read current
const response = await fetch(`${apiBase}/api/v4/core/PRD/${productId}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
const current = await response.json();
// 2. Modify
const updated = {
...current.data,
XPRD02: newPrice
};
// 3. Write
const result = await fetch(`${apiBase}/api/v4/core/PRD/${productId}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ data: updated })
});
return result.json();
}
Pattern 2: Bulk Update
Update multiple records:
async function bulkUpdateCategory(productIds, newCategory) {
const updates = productIds.map(async (id) => {
// Fetch current
const response = await fetch(`${apiBase}/api/v4/core/PRD/${id}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
const current = await response.json();
// Update category
const updated = {
...current.data,
XPRD05: newCategory
};
// PUT
return fetch(`${apiBase}/api/v4/core/PRD/${id}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ data: updated })
});
});
return Promise.all(updates);
}
// Usage
await bulkUpdateCategory([123, 124, 125], 'cat_electronics');
Pattern 3: Conditional Update
Update only if condition met:
async function updatePriceIfBelowThreshold(productId, newPrice, threshold) {
// Fetch current
const response = await fetch(`${apiBase}/api/v4/core/PRD/${productId}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
const current = await response.json();
// Check condition
if (current.data.XPRD02 >= threshold) {
throw new Error(`Price ${current.data.XPRD02} is above threshold ${threshold}`);
}
// Update
const updated = {
...current.data,
XPRD02: newPrice
};
const result = await fetch(`${apiBase}/api/v4/core/PRD/${productId}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ data: updated })
});
return result.json();
}
Best Practices
✅ DO:
Fetch before PUT (read-modify-write):
// ✅ Good - safe update
const current = await getProduct(123);
const updated = { ...current.data, XPRD02: 109.99 };
await updateProduct(123, updated);
Use PATCH instead for partial updates:
// ✅ Good - use PATCH for single field
PATCH /api/v4/core/PRD/123
{ "data": { "XPRD02": 109.99 } }
Handle concurrent modifications:
// ✅ Good - version check
if (current.LDATA !== expectedVersion) {
throw new Error('Record modified by another user');
}
Include all fields in PUT:
// ✅ Good - complete record
PUT /api/v4/core/PRD/123
{
"data": {
"XPRD01": "...",
"XPRD02": "...",
"XPRD04": "...",
"XPRD05": "...",
"XPRD06": "..."
}
}
❌ DON'T:
Don't PUT without reading first:
// ❌ Bad - might lose fields
PUT /api/v4/core/PRD/123
{
"data": {
"XPRD02": 109.99
// Missing other fields → they'll be cleared!
}
}
Don't use PUT for single field updates:
// ❌ Bad - use PATCH instead
PUT /api/v4/core/PRD/123
{
"data": {
"XPRD01": "...",
"XPRD02": 109.99, // Only changing this
"XPRD04": "...",
"XPRD05": "...",
"XPRD06": "..."
}
}
// ✅ Good
PATCH /api/v4/core/PRD/123
{ "data": { "XPRD02": 109.99 } }
Don't ignore concurrent modifications:
// ❌ Bad - last write wins (data loss)
await updateProduct(123, data1); // User A
await updateProduct(123, data2); // User B (overwrites A's changes)
// ✅ Good - version check
await updateWithVersionCheck(123, data, expectedVersion);
Summary
- ✅ PUT performs full record updates, sets TREC='M'
- ✅ Replaces ALL fields - missing fields set to NULL/default
- ✅ Use PATCH for partial updates (safer)
- ✅ COD_ON_OFF='M' flag required for field to be modifiable
- ✅ UPDATE_CASCADE propagates changes to related records
- ✅ Always fetch-modify-write to avoid data loss
- ✅ Outbox event published to RabbitMQ
Key Takeaways:
- PUT replaces entire record - use carefully
- Fetch current record before PUT (read-modify-write pattern)
- Use PATCH for single field updates
- Check COD_ON_OFF='M' flag for modifiable fields
- Handle concurrent modifications with version checks
- TREC automatically transitions to 'M'
Next: Partial Updates → for safer field updates
Related Concepts
- Partial Updates - PATCH operations
- TREC Filtering - Query by TREC state
- Entity Lifecycle - TREC state machine
- Field Visibility - COD_ON_OFF flags