An ERP API can look healthy in development and still fail when finance, inventory, sales, and warehouse users hit the same transaction path concurrently. The common causes are not usually the HTTP layer alone. They include oversized database transactions, repeated ORM queries, synchronous integrations, lock contention, and background jobs competing with interactive requests.
This is where ERP Development Services need to be treated as an architecture problem, not simply a customization exercise. A useful starting point is to separate transactional APIs from long-running work, establish clear database boundaries, and make every integration observable.
If you are evaluating custom ERP development services, the same principles apply whether the backend uses ERPNext, Odoo, or a custom Python service.
Context and Setup
The target architecture is a multi-module ERP serving sales, procurement, inventory, finance, and reporting workloads.
A practical stack is:
- Python with FastAPI for service APIs
- PostgreSQL for transactional data
- Redis for short-lived caching and distributed coordination
- Docker for reproducible deployments
- AWS ECS or Kubernetes for application workloads
- SQS for asynchronous jobs
- OpenTelemetry for traces and metrics
The important architectural boundary is between transactional commands and expensive workflows.
For example, creating a purchase order should return after the database transaction and required validation complete. Sending supplier notifications, generating analytics events, or synchronizing a third-party system can happen asynchronously.
This matters because benchmark results show how much infrastructure can influence application throughput. In TechEmpower's 2025 Round 23, top-performing frameworks saw roughly a 3x improvement in practical network-bound tests after the benchmark hardware and network environment were upgraded. That is not an ERP benchmark, but it demonstrates why application code should not be evaluated independently from its runtime environment.
Designing ERP Development Services Around Transaction Boundaries
The first design rule is simple: keep the critical transaction path small and deterministic.
Step 1: Separate commands from background work
An ERP request often triggers several secondary operations. Do not execute all of them inside the user's HTTP request.
Use a command such as:
POST /purchase-orders
|
v
Validate request
|
v
Database transaction
|
+---- Purchase Order
+---- Line Items
+---- Audit Event
|
v
Return 201
|
v
Queue async work
|
+---- Supplier notification
+---- ERP synchronization
+---- Analytics
The API becomes easier to reason about because the transaction owns only data that must be consistent before the response is returned.
Step 2: Make database access intentional
ORM convenience can hide expensive query patterns. An ERP endpoint that loads an order, customer, warehouse, pricing rules, tax records, and inventory rows separately can quickly become a query multiplier.
A compact FastAPI example:
@app.post("/purchase-orders")
async def create_purchase_order(payload: PurchaseOrderInput):
async with db.transaction(): # Why: keeps financial and stock records atomic
order = await create_order(payload)
await create_order_lines(order.id, payload.lines)
await audit_log(
entity_id=order.id,
action="purchase_order_created"
) # Why: audit data belongs to the same consistency boundary
await queue.publish({
"event": "purchase_order.created",
"order_id": order.id
}) # Why: notifications and integrations do not block the request
return {"id": order.id}
The code intentionally does not call an external supplier API while the database transaction is open.
That distinction is critical. A slow third-party API can otherwise hold database locks longer than expected and increase contention for unrelated ERP users.
Step 3: Add idempotency and observability
ERP systems process financial and inventory events where duplicate execution can be costly.
Every externally retried command should have an idempotency key:
key = request.headers["Idempotency-Key"]
if await cache.exists(f"erp:idempotency:{key}"):
return await load_previous_result(key)
result = await process_order(payload)
await cache.set(
f"erp:idempotency:{key}",
result,
ttl=86400
) # Why: prevents duplicate processing after client retries
Then instrument the request with:
- API latency
- Database query duration
- Transaction duration
- Queue delay
- External API latency
- Error rate
- Retry count
A single average response-time number is not enough. For ERP workloads, p95 and p99 latency are often more useful because a small percentage of slow transactions can affect users during peak operational periods.
Real-World Application
In one of our ERPNext implementations at Oodles, Family Doctor Australia required centralized ERP operations across 114 medical and dental practices. The architecture had to account for geographically distributed operations, standardized workflows, secure access, and centralized governance rather than treating each location as an isolated system.
The implementation used ERPNext as the central ERP foundation and focused on standardized workflows and controlled access across the distributed organization. This is a useful example of why ERP Development Services must consider organizational topology alongside application code: the number of operational locations directly affects permissions, data boundaries, reporting, and deployment decisions.
Oodles also worked on Texas Flange, where ERPNext was adapted around a job-centric distribution model. Each unique job became the primary operational entity, with sales, procurement, landed costs, and financial controls connected to that job. The architecture was therefore shaped by transaction semantics rather than by simply enabling standard ERP modules.
You can explore more engineering and implementation work from Oodles.
Key Takeaways
- Keep database transactions limited to operations that require atomic consistency.
- Move notifications, analytics, synchronization, and other long-running work to queues.
- Inspect ORM-generated SQL instead of assuming a small amount of application code means few database queries.
- Use idempotency keys for commands that may be retried by browsers, integrations, or message consumers.
- Measure p95/p99 latency, transaction duration, queue delay, and external API time separately.
Conclusion
Good ERP architecture starts by identifying which operations require immediate consistency and which can tolerate asynchronous execution.
For developers, that usually means smaller transactions, explicit database access, queue-backed integrations, and instrumentation from the first production release. For architects, it means designing around business transaction boundaries rather than around ERP modules alone.
That approach makes ERP Development Services a measurable engineering discipline: define the workload, isolate contention, instrument the critical paths, and test the system under realistic concurrency.
Discuss the Architecture
If you are designing a high-concurrency ERP, migrating from a legacy platform, or deciding between customization and a custom backend, share your architecture or constraints in the comments.
For a technical discussion about ERP Development Services, ERP Development Services can be used to start a conversation with the Oodles engineering team.
FAQ
1. What are ERP Development Services?
ERP Development Services involve designing, developing, customizing, integrating, testing, and maintaining enterprise resource planning software. They can include custom modules, ERPNext or Odoo customization, API development, third-party integrations, workflow automation, data migration, security, and cloud deployment.
2. How do you improve ERP API performance?
Improve ERP API performance by reducing database round trips, indexing high-frequency queries, keeping transactions short, caching suitable read-heavy data, moving slow operations to background workers, and measuring p95 and p99 latency under realistic concurrent workloads.
3. Should ERP integrations run synchronously?
Only integrations that are required before a transaction can be considered complete should run synchronously. Supplier notifications, analytics, reporting updates, and many third-party synchronizations should usually run asynchronously through a durable queue so external latency does not extend the main database transaction.
4. Is PostgreSQL suitable for custom ERP systems?
PostgreSQL is suitable for custom ERP systems because it provides transactional consistency, indexing, constraints, mature SQL capabilities, and strong support for concurrent workloads. The 2024 Stack Overflow Developer Survey reported PostgreSQL at 49% usage among respondents, making it the survey's most-used database for the second consecutive year.
5. When should a company consider ERP Development Services?
A company should consider ERP Development Services when standard ERP workflows cannot represent important business rules, integrations require substantial customization, multiple systems create inconsistent data, or operational processes need a specialized workflow that off-the-shelf configuration cannot provide.
Top comments (0)