Tender platform ingestion and alerts
CLIENT
Achizito
TECHNOLOGIES
- TypeScript
- Node.js
- RabbitMQ
- Redis
- .NET
- CQRS
WEBSITE
PreviewCase studies
Other usecasesDescription
Achizito helps companies win Romanian public tenders by tracking notices published on SEAP and alerting users as soon as something relevant appears.
The product promise is simple and strict: real time alerts in under one second. If an alert is late, the customer loses bidding time.
This engagement focused on keeping the ingestion edge separate from the core domain model so the team could move fast on source intake while the modular monolith stayed clean and predictable.
Key achievements:
- Sub second alert flow from source update to alert dispatch
- Node.js ingestion reuse from an existing project the team already had
- Modular monolith boundaries preserved in the .NET core domains
- RabbitMQ based event flow between ingestion and business domains
- Redis based caching for LLM and repeated lookups
- Single database for speed and consistency across the platform
Original context
The platform is a .NET modular monolith with Procedures, Documents, Requirements, Notifications, and Analytics.
Solution approach
1. Reuse the Node.js ingestion project
We found an existing Node.js ingestion project that already solved the source intake problem, so we reused it as the ingestion edge.
That let us focus on integration, module boundaries, and event flow instead of rebuilding source intake from scratch.
2. Keep the .NET modular monolith as the domain model
The core stays as one modular monolith.
Each domain owns its own rules and data, and the business logic stays in one place where the team can reason about it.
3. Use RabbitMQ for real time handoff
The ingestion edge sends real time work through RabbitMQ queues.
That gives the platform a clean boundary between intake and processing.
4. Use Redis for repeated reads
Redis is used to cache LLM-related data and repeated reads that do not need to hit the core every time.
That keeps the platform responsive while the core continues to own the business truth.
5. Single database on purpose
The platform keeps one shared database on purpose.
That gives the team faster development, simpler querying, and a consistent view across ingestion and core domains.
The modules still keep separate responsibilities in code. The shared database is only the practical persistence layer that lets the monolith stay simple to evolve.
6. Explicit division of responsibilities
| Concern | Owner |
|---|---|
| Ingesting SEAP updates and normalization | TypeScript / Node.js |
| Queueing and real time handoff | RabbitMQ |
| Subscriptions and business rules | .NET |
| Market analytics | .NET |
| AI extraction and eligibility | .NET + Microsoft Agent Framework |
| Notification policy and delivery decisions | .NET |
The TypeScript ingestion edge owns intake behavior. The .NET core owns the domain model, analytics, and decisioning.
Requirements and domain interaction
The platform needed a few non-negotiable behaviors:
- Alerts must be generated quickly enough to preserve bidding time.
- Ingestion must stay separate from procurement rules.
- Analytics must remain consistent with the same procedure data that drives alerts.
- Notifications must be driven by domain behavior, not by raw source noise.
- AI workflows must stay in the core where procedure, requirement, and notification context already exists.
The important point is that ingestion feeds the model, but does not absorb it.
Modular monolith boundaries
The core is split into clear subdomains with separate responsibilities:
- Procedures - the main procurement lifecycle and state of a tender.
- Documents - procedure files, attachments, and extracted content.
- Requirements - matching and evaluation of what the procedure asks for.
- Notifications - alert decisions and delivery triggers.
- Companies - business entities and their identity in the platform.
- Analytics - reporting, trends, and market intelligence.
These subdomains do not call each other directly for business changes. They interact through domain events, which keeps each responsibility narrow and makes the modular monolith easier to change safely.
Results
| Outcome | Impact |
|---|---|
| Real time alerting | Ingestion remains optimized for low-latency source updates |
| Domain clarity | Core business logic stays in explicit .NET bounded contexts |
| Coupling reduction | Ingestion and core interact through queues and shared domain contracts |
| Maintainability | TypeScript ingestion can evolve independently from core domain rules |
| Platform resilience | Domain boundaries make failures easier to isolate and recover |
Conclusions
This project delivered real-time procurement alerts for a tender platform while improving domain boundaries instead of eroding them. The architecture balances ingestion speed, modular ownership, and long-term maintainability.
The next phase is controlled evolution: keep the modular monolith clean, keep the single database as the practical source of truth, and preserve sub-second user-facing alert behavior.