-
Notifications
You must be signed in to change notification settings - Fork 18
Architecture Overview
phoss-ap is an open-source Peppol Access Point built on phase4 and Spring Boot.
| Component | Technology | Purpose |
|---|---|---|
| AS4 Engine | phase4 | AS4 message sending and receiving |
| Database | PostgreSQL | Persistent storage of transactions, metadata, and reporting data |
| API | Spring Boot REST | Inbound API for triggering sends and reporting |
| Verification | Pluggable SPI (optional) | Validation of outbound and inbound documents before processing |
| Forwarding | Pluggable (per instance) | Delivering received documents to the Receiver Backend |
| Retry | Customizable Java interfaces | Automatic retry of failed sends and failed forwarding |
| Notifications | Generic Java interface | Alerting on permanent failures |
| Reporting | peppol-reporting | Peppol TSR/EUSR generation |
C1 (Sender) --> C2 (Sender AP) --> C3 (Receiver AP) --> C4 (Receiver)
phoss-ap operates exclusively on the C2-to-C3 boundary. It does not implement C1 or C4 functionality. The systems that interact with the AP on either side are:
- Sender Backend: An internal system within C2 that submits documents to the AP for outbound transmission.
- Receiver Backend: An internal system within C3 that receives documents from the AP after inbound reception.
[Sender Backend] --> [phoss-ap (C2)] --AS4--> [Remote AP (C3)]
[Remote AP (C2)] --AS4--> [phoss-ap (C3)] --> [Receiver Backend]
The AP accepts outbound documents from the Sender Backend via its API in two forms:
- Raw business document + metadata: The AP creates the SBDH envelope. The Sender Backend provides sender ID, receiver ID, document type ID, process ID, and C1 country code as API parameters.
- Pre-built SBD: The Sender Backend provides a complete Standard Business Document (with SBDH). All metadata (including C1 country code) is extracted from the SBDH.
Received documents are forwarded to the Receiver Backend. The forwarding mode is configured per AP instance (one mode per deployment):
- HTTP POST (async reporting): Document is POSTed to the Receiver Backend. The Receiver Backend calls the reporting API asynchronously later.
- HTTP POST (sync reporting): Document is POSTed to the Receiver Backend. The Receiver Backend responds synchronously with the C4 country code, and the AP triggers reporting immediately.
- S3 + link forwarding: Document is stored in S3, and a link/reference is forwarded to the Receiver Backend. Reporting is asynchronous.
- SFTP: Document is uploaded via SFTP. Reporting is asynchronous.
Before processing an inbound document, the AP verifies that it actually services the addressed receiver participant for the given document type and process. This check is implemented via a pluggable SPI (IReceiverCheck or similar) defined in phoss-ap-api.
Possible implementations include:
- Querying the AP's own SMP registration
- Checking a local configuration or database
If the receiver is not serviced, the AP returns an AS4 error with code EBMS:0004 / PEPPOL:NOT_SERVICED to the sending AP (C2). No database entry is created for rejected messages.
This check occurs before database storage and document verification, ensuring that unserviced messages are rejected as early as possible with the correct Peppol-defined error code.
Both outbound and inbound documents can optionally be verified before further processing. Verification is implemented via a generic plugin-like SPI (IDocumentVerifier or similar) defined in phoss-ap-api.
-
Outbound: After storing the document in the database, the verification plugin is invoked before AS4 sending. If verification fails, the transaction is marked as
rejected, a notification is triggered, and no sending attempt is made. -
Inbound: After storing the received document in the database, the verification plugin is invoked before forwarding to the Receiver Backend. If verification fails, the transaction is marked as
rejected, a notification is triggered, and no forwarding attempt is made.
The verification step is optional — if no plugin is configured, processing continues without verification. Multiple verification plugins can be chained (all must pass).
Duplicate detection operates on two independent levels:
- AS4 Message ID: Detects retransmission of the exact same AS4 message.
- SBDH Instance Identifier: Detects the same Peppol business envelope sent again (potentially with a different AS4 Message ID).
Each level is tracked independently in the database. The behavior on detection is configurable:
- Reject: Duplicate messages are rejected at the AS4 level.
- Store and flag: Duplicates are stored in the DB but flagged per level.
MLS is an asynchronous response mechanism defined by Peppol that allows the receiving AP (C3) to inform the sending AP (C2) about the processing outcome of a received document.
| Code | Meaning | Description |
|---|---|---|
| RE | Rejection | Document failed validation or delivery to C4 permanently |
| AP | Approved | Successful delivery with confirmation from C4 |
| AB | Accepted Blind | Document forwarded without delivery confirmation capability |
- C2 sends a business document to C3 via AS4.
- C3 receives the document, forwards it to the Receiver Backend, and determines the outcome.
- C3 generates an MLS response and sends it back to C2 as a separate AS4 message.
- C2 receives the MLS and correlates it with the original outbound transaction via the SBDH Instance Identifier.
MLS messages are themselves regular Peppol documents sent via AS4, so they reuse the existing outbound transaction infrastructure (including retry logic).
- MLS_TO: An alternative Peppol participant ID where C3 should send the MLS response. Configurable per outbound transaction (set by the Sender Backend via the API). Useful when C2 operates multiple access points.
-
MLS_TYPE: Controls when MLS is sent —
FAILURE_ONLY(only on rejection) orALWAYS_SEND(for all outcomes). Configurable per AP instance. The active value is captured per inbound transaction at reception time.
- As C2 (sending): The AP receives MLS responses asynchronously via AS4, correlates them with outbound transactions, and updates the MLS status.
-
As C3 (receiving): After determining the forwarding outcome, the AP generates and sends an MLS response back to C2. The MLS sending is tracked as an
outbound_transactionwithtransaction_type = mls_response.
The Peppol Network Policy defines three MLS milestones based on AS4 MessageInfo/Timestamp values (all UTC):
| Milestone | Definition | Where captured |
|---|---|---|
| M1 | AS4 Timestamp when the original business document transmission is initiated at C2 |
outbound_sending_attempt.as4_timestamp (outbound) or inbound_transaction.as4_timestamp (inbound) |
| M2 | AS4 Timestamp when the MLS message transmission is initiated at C3 |
outbound_sending_attempt.as4_timestamp on the MLS response's sending attempt |
| M3 | Date/time when the MLS message is received at C2 | outbound_transaction.mls_received_dt |
Service Level Requirements (per Peppol Network Policy, monthly measurement, for payloads < 10 MB):
| SLR | Constraint | Measurement |
|---|---|---|
| SLR MLS-1 (Latest MLS Sending) | 99.5% of MLS messages: M2 - M1 <= 20 minutes |
inbound_transaction.as4_timestamp (M1) vs MLS outbound_sending_attempt.as4_timestamp (M2) |
| SLR MLS-2 (Latest MLS Reception) | 99.5% of MLS messages: M3 - M1 <= 25 minutes |
inbound_transaction.as4_timestamp (M1) vs outbound_transaction.mls_received_dt (M3) |
phoss-ap supports running multiple instances against the same PostgreSQL database for high availability and horizontal scaling.
All DB-based schedulers and checks use PostgreSQL SELECT ... FOR UPDATE SKIP LOCKED to safely distribute work across instances without requiring leader election:
-
Retry scheduler (outbound sending and inbound forwarding): Each instance picks up eligible transactions independently.
SKIP LOCKEDensures no two instances process the same transaction concurrently. -
Archival scheduler: Each instance picks up completed transactions to archive. Same
SKIP LOCKEDapproach prevents double-moves. -
Duplicate detection: Protected by unique constraints on
as4_message_idandsbdh_instance_id. Concurrent inserts are safe — the second insert fails and is handled gracefully. - MLS correlation: DB-based lookup by SBDH Instance Identifier. No instance-affinity required.
Peppol reporting (TSR/EUSR) submission must happen exactly once per reporting period. This uses a leader-based approach controlled via configuration:
-
reporting.leader.enabled: Whentrue, this instance acts as the reporting leader and submits Peppol reports on schedule. Default isfalse.
Exactly one instance should have this set to true. All other instances set it to false.
The per-C3 circuit breaker is held in-memory per instance. Each instance independently tracks the health of C3 endpoints it communicates with. This is acceptable — each instance protects itself, and a restart clears all breaker state (which acts as a natural recovery).
Notifications are triggered by the instance that performs the action (e.g., the instance that exhausts retries triggers the permanent failure notification). Since work distribution via SKIP LOCKED ensures only one instance handles a given transaction, duplicate notifications do not occur.
Transactions that are fully completed successfully are moved from the primary DB tables to backup/archive tables to prevent table bloat. Failed transactions remain in the primary tables.
On shutdown, the AP:
- Stops accepting new inbound AS4 messages and new outbound API submissions.
- Finalizes any messages currently being processed (in-flight AS4 sends and forwarding operations).
This ensures no transactions are left in an inconsistent state.
The AP forwards the complete SBD (Standard Business Document = SBDH + business document) to the Receiver Backend. The Receiver Backend is responsible for parsing the SBDH and extracting the business document if needed.
A generic Java interface is provided for failure notifications. Concrete implementations are deployment-specific (e.g., email, Slack, monitoring system webhook). Notification triggers include:
- Outbound document verification rejection
- Inbound document verification rejection
- Permanent sending failure (max retries exhausted)
- Permanent forwarding failure on the receiving side
It is appreciated if you star the GitHub project if you like it.
Donation link: https://paypal.me/PhilipHelger
- Home
- News and noteworthy
- Running phoss AP
- Architecture Overview
- API Specification
- Configuration Properties
- Code Lists
- Database Design Notes
- Maven Module Structure
- Runtime Extensions
- OpenTelemetry Integration
- Security Considerations
- Peppol Specifics
- Testing Without Peppol Network
- Known Users
- Migrating from phase4-peppol-standalone
- Contributing