nexgate/orchestrator
the orchestrator module, member of GSF-nexgate ZUGFeRD family.
The orchestrator module is the central control unit and main entry point for Go applications using NexGate. It
manages the high-level invoice generation lifecycle, transforms incoming JSON payloads into internal domain models,
resolves multi-tenant configurations, and delegates processing to specialized writer engines.
Core Responsibilities
- Configuration Management: Initializes system parameters (
system.yaml) and tenant settings (seller.yaml).
- Payload Validation & Parsing: Transforms incoming JSON payloads into the strongly typed
MasterZugFerd domain structure.
- Pipeline Execution: Coordinates rendering steps across engines (e.g., XML generation $\rightarrow$ PDF layout $\rightarrow$ ZUGFeRD PDF injection).
- Engine Delegation: Uses
api/writer factory logic to dynamically instantiate and invoke the required engines (native vs. container).
Module Directory Structure
orchestrator/
├── config/ # Configuration loaders (system.yaml, seller.yaml, environment paths)
├── orchestrator.go # Core Orchestrator struct, New/NewDefault constructors, and RunJsonJob entry point
├── generateBase.go # Pipeline step: Pure XML / PAR generation
├── generatePdf.go # Pipeline step: Layout PDF generation via LibreOffice
├── generateZugferd.go # Pipeline step: Hybrid ZUGFeRD / Factur-X assembly via Mustang
└── orchestrator_test.go # Comprehensive integration test suite (the reference example)
Architecture & Data Flow
The orchestrator abstracts the complex multi-step rendering process behind a clean, single-method API (RunJsonJob).
+---------------------------------------+
| Application Call: orch.RunJsonJob() |
+-------------------+-------------------+
|
v
+---------------------------------------+
| 1. Parse JSON -> MasterZugFerd Struct |
+-------------------+-------------------+
|
v
+---------------------------------------+
| 2. Resolve Tenant & System Configs |
+-------------------+-------------------+
|
+---------------------+---------------------+
| | |
v v v
+--------------------+ +--------------------+ +--------------------+
| generateBase() | | generatePdf() | | generateZugferd() |
| (XML Generation) | | (LibreOffice PDF) | | (Mustang Assembly) |
+----------+---------+ +----------+---------+ +----------+---------+
| | |
+---------------------+---------------------+
|
v
+---------------------------------------+
| 3. Finalize Output & Return OutputPath |
+---------------------------------------+
Internal Pipeline Mechanics (generatePdf.go)
Each rendering step in the orchestrator follows a unified, declarative pattern:
- Configuration Resolution: Reads provider flags (
native, container), timeouts, and engine paths from config.SystemParams.
- Writer Instantiation: Requests a concrete
writer.Writer from the central registry via writer.CreateWriter().
- Environment Path Assembly: Constructs the required
RenderInput paths (ArtefactsPath, RootPath, OutputPath) based on tenant settings.
- Execution: Calls
pdfWriter.Render(), delegating the actual execution to the engine.
Implementation Pattern:
func (o *Orchestrator) generatePdf(rJob *job.RenderJob, finalize bool) (*writer.RenderOutput, error) {
engine := config.SystemParams.Engine.PdfEngine // e.g., loEngine.odt
provider := config.SystemParams.Engine.PdfProvider // "native" | "container"
kind := writer.PdfKind
timeout := config.SystemParams.Engine.CmdTimeout
heartbeat := config.SystemParams.Engine.HeartbeatFrequency
// 1. Obtain registered writer instance
pdfWriter, err := writer.CreateWriter(kind, provider, engine, timeout, heartbeat)
if err != nil {
return nil, err
}
// 2. Prepare paths within the nexgate-env structure
renderInput := writer.RenderInput{
OttPath: o.ZugferdMaster.Paths.OTTfile,
ArtefactsPath: filepath.Join(config.SellerParams.SellerRoot, "data", "artefacts"),
RootPath: filepath.Join(config.SellerParams.SellerRoot, "data", "pdfs"),
OutputPath: filepath.Join(config.SellerParams.SellerRoot, "outputs", o.ZugferdMaster.Invoice.InvoiceID+".pdf"),
}
// 3. Delegate rendering
return pdfWriter.Render(o.ctx, rJob, &renderInput, finalize)
}
Job Payload Specification (invoice.json)
The orchestrator consumes JSON payloads to generate invoices. The payload consists of three main top-level sections:
mandant (optional): Defines the tenant/seller context. If omitted, the default tenant or environment configuration is used.
options (optional): Controls rendering behavior, target queue, custom template overrides, and file attachments. If queue is omitted, it defaults to full ZUGFeRD processing.
invoice (required): The core business data (buyer, line items, VAT breakdown, totals, and payment terms).
Payload Structure Overview
| Section |
Mandatory? |
Description |
mandant.seller |
No |
Overrides the target tenant folder (e.g., "testSeller" $\rightarrow$ env/sellers/testSeller). |
options.queue |
No |
Target pipeline queue ("pdf", "facturx", "base"). Defaults to "zugferd". |
options.template |
No |
Path to a custom LibreOffice template (.ott). Overrides seller default. |
options.attachments |
No |
Array of file paths to be embedded into the hybrid ZUGFeRD PDF. |
invoice.currency |
Yes |
ISO 4217 currency code (e.g., "EUR"). |
invoice.buyer |
Yes |
Buyer address and metadata. |
invoice.items |
Yes |
Array of line items (quantity, net price, tax rates). |
invoice.vats |
Yes |
Grouped VAT totals required for tax compliance. |
invoice.totals |
Yes |
Calculated totals (line_total_amount, tax_total_amount, grand_total_amount). |
Examples
Minimal Payload (min-invoice.json)
Demonstrates the bare minimum required to generate a valid invoice. Uses default seller settings and default ZUGFeRD pipeline:
{
"invoice": {
"currency": "EUR",
"invoice_id": "RE-0814-min",
"issue_date": "20260105",
"service_date": "20260104",
"buyer": {
"id": "471115",
"name1": "Franz Fröhlich AG",
"street": "Musterstrasse 64",
"zip": "01221",
"city": "Musterdorf",
"country": "Deutschland"
},
"items": [
{
"pos": 1,
"description": "Go Entwicklungsservice",
"quantity": 10.5,
"unit": "Stunde",
"net_price": 95.00,
"line_total": 997.5,
"tax_rate": 19
}
],
"vats": [
{
"pos": 1,
"id": "S",
"description": "USt",
"vat_base": 997.50,
"vat_amount": 189.53,
"tax_rate": 19
}
],
"totals": {
"line_total_amount": 997.50,
"tax_total_amount": 189.53,
"grand_total_amount": 1187.03
}
}
}
Advanced Payload (max-invoice.json)
Demonstrates multi-tenant override (mandant), custom template & PDF attachments (options),
extended header notes, payment terms, and multi-VAT breakdowns:
{
"mandant": {
"seller": "testSeller"
},
"options": {
"queue": "facturx",
"template": "testdata/data/test_generate_max_template.ott",
"attachments": [
"testdata/data/test_attachment.png",
"testdata/data/test_attachment.pdf"
]
},
"invoice": {
"currency": "EUR",
"invoice_id": "RE-0815-max",
"issue_date": "20260105",
"service_start": "20250104",
"service_end": "20251231",
"buyer_reference": "991-12345-76",
"buyer": {
"id": "471115",
"name1": "Franz Fröhlich AG",
"street": "Musterstrasse 64",
"zip": "01221",
"city": "Musterdorf",
"country": "Österreich",
"email": "bernd.buyer@email.com"
},
"payment_terms": {
"due_description": "innerhalb von 3 Tagen mit 3% Skonto.",
"due_date": "20260302"
},
"items": [ ... ],
"vats": [ ... ],
"totals": { ... }
}
}
Further Variants: Additional JSON test fixtures covering edge cases (e.g. credit notes, foreign currencies, zero-tax exports) can be found in testdata/resources/.
API Entry Points
// 1. Initialize Orchestrator with default environment resolution
orch, err := orchestrator.NewDefault()
// 2. Execute a complete invoice rendering pipeline from a JSON byte stream
result, err := orch.RunJsonJob(jsonBytes)
Integration Testing
The orchestrator includes a comprehensive integration test suite designed to verify complex, real-world execution pipelines (including PDF/A conversion, document merging, attachment processing, and environment management).
Depending on your local system setup and requirements, you can choose between Native Engine Execution and full Containerized Pipeline Testing.
Test Suites Overview
| Test Suite File |
Engine Modes |
Prerequisite |
Primary Use Case |
orchestrator_test-suite_native_test.go |
NATIVE only |
Standard Go toolchain (No Docker/Podman required) |
Quick local development, lightweight test runs, initial onboarding |
orchestrator_suite_test.go |
NATIVE & CONTAINER |
Active Container Runtime (Docker / Podman) |
Production parity tests, CI/CD pipelines, full container isolation testing |
1. Native Testing (Recommended for Getting Started)
If you do not have Docker or Podman installed—or simply want a lightweight, fast local test run—use the native test suite.
# Run only the native integration test suite
go test -v -run TestOrchestratorSuiteNative ./...