Prerequisites
Core Concepts
Calculators
Calculators are the atomic pricing units (price lists):- SIMPLE_FIXED - Flat fee (e.g., 25 PLN)
- PERCENTAGE - Percentage of base amount (e.g., 23% VAT)
- STEP_FUNCTION - Tiered pricing (e.g., 2 PLN per GB)
- LINEAR - Continuous linear pricing
Components
Components wrap calculators and compose into hierarchies:- Simple Component - Wraps a single calculator
- Composite Component - Combines multiple components
- Components can depend on each other’s values
Step-by-Step Tutorial
import com.softwarearchetypes.pricing.*;
import com.softwarearchetypes.quantity.money.Money;
import java.math.BigDecimal;
import java.time.*;
Clock clock = Clock.systemDefaultZone();
PricingConfiguration config = PricingConfiguration.inMemory(clock);
PricingFacade facade = config.pricingFacade();
// Fixed base fee: 45 PLN
facade.addCalculator(
"base-fee-calc",
CalculatorType.SIMPLE_FIXED,
Parameters.of("amount", Money.pln(BigDecimal.valueOf(45)))
);
// Data overage: 2 PLN per GB
facade.addCalculator(
"data-overage-calc",
CalculatorType.STEP_FUNCTION,
Parameters.of(
"basePrice", Money.pln(BigDecimal.ZERO),
"stepSize", BigDecimal.ONE,
"stepIncrement", BigDecimal.valueOf(2)
)
);
// VAT: 23%
facade.addCalculator(
"vat-calc",
CalculatorType.PERCENTAGE,
Parameters.of("percentageRate", BigDecimal.valueOf(23))
);
// Base fee component
facade.createSimpleComponent("base-fee", "base-fee-calc");
// Data overage component
facade.createSimpleComponent("data-overage", "data-overage-calc");
// VAT component
facade.createSimpleComponent("vat", "vat-calc");
// Base fee (no parameters needed)
Money baseFee = facade.calculateComponent("base-fee", Parameters.empty());
System.out.println("Base fee: " + baseFee); // 45 PLN
// Data overage for 3 GB
Parameters dataParams = Parameters.of("quantity", BigDecimal.valueOf(3));
Money dataCharge = facade.calculateComponent("data-overage", dataParams);
System.out.println("Data overage: " + dataCharge); // 6 PLN (3 * 2)
// VAT on 100 PLN
Parameters vatParams = Parameters.of("baseAmount", BigDecimal.valueOf(100));
Money vat = facade.calculateComponent("vat", vatParams);
System.out.println("VAT: " + vat); // 23 PLN
import java.util.Map;
// Net amount = base fee + data overage
facade.createCompositeComponent(
"net-amount",
Map.of(), // No parameter mappings needed
"base-fee",
"data-overage"
);
// Calculate net amount
Money netAmount = facade.calculateComponent("net-amount", dataParams);
System.out.println("Net amount: " + netAmount); // 51 PLN (45 + 6)
// Total bill = net amount + VAT (where VAT depends on net amount)
facade.createCompositeComponent(
"total-bill",
Map.of(
"vat", Map.of(
"baseAmount", new ValueOf("net-amount") // VAT uses net-amount's value
)
),
"net-amount",
"vat"
);
// Calculate total with dependency resolution
Money total = facade.calculateComponent("total-bill", dataParams);
System.out.println("Total: " + total); // 62.73 PLN (51 + 11.73)
ComponentBreakdown breakdown = facade.calculateComponentBreakdown(
"total-bill",
dataParams
);
System.out.println("Component: " + breakdown.name());
System.out.println("Total: " + breakdown.total());
System.out.println("Children: " + breakdown.children().size());
// Navigate the hierarchy
for (ComponentBreakdown child : breakdown.children()) {
System.out.println(" - " + child.name() + ": " + child.total());
for (ComponentBreakdown grandChild : child.children()) {
System.out.println(" - " + grandChild.name() + ": " + grandChild.total());
}
}
Real-World Example: Telecom Billing
Let’s build a complete mobile subscription pricing model:Advanced Patterns
Pattern: Tiered Pricing
Create volume discounts:Pattern: Time-Based Pricing
Use applicability constraints for seasonal pricing:Pattern: Conditional Components
Add components that only apply under certain conditions:Pattern: Component Versioning
Create multiple versions of pricing:E-Commerce Example
Simulation and What-If Analysis
Test pricing changes before deployment:Next Steps
- Learn about Component Versioning for price history
- Explore Applicability Constraints for complex rules
- See Multi-Currency Pricing for international sales
The Pricing module separates what to charge (calculators) from how to compose (components). This enables flexible pricing evolution without code changes.
