-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Description
Overview
New monorepo qqq-qbits-carrier providing a service-provider pattern for carrier integrations. Applications pull in only the carriers they need.
Architecture
qqq-qbits-carrier/
├── qqq-qbit-carrier-api/ # Interfaces, DTOs, registry
├── qqq-qbit-carrier-fedex/ # FedEx REST API implementation
├── qqq-qbit-carrier-usps/ # USPS REST API implementation
└── qqq-qbit-carrier-ups/ # UPS REST API implementation (future)
Key Design Decisions
- Service-provider pattern: All carriers implement
CarrierServiceProviderinterface - Runtime resolution:
CarrierServiceRegistry(singleton, ConcurrentHashMap) resolves carrier by name - Selective inclusion: Each carrier is a separate Maven module -- apps depend only on what they use
- NOT in QQQ core: All modules live in a separate monorepo, depend on
qqq-backend-corefor QBitConfig/QBitProducer interfaces only - OAuth2 per carrier: Each carrier manages its own token lifecycle transparently
Module: qqq-qbit-carrier-api (Common Interfaces)
CarrierServiceProvider Interface
public interface CarrierServiceProvider
{
String getCarrierName();
CarrierAddressValidationResult validateAddress(CarrierAddressValidationRequest request);
CarrierLabelResult generateLabel(CarrierLabelRequest request);
void voidLabel(String trackingNumber);
CarrierTrackingResult trackPackage(String trackingNumber);
CarrierRateResult getRates(CarrierRateRequest request);
CarrierLabelResult generateReturnLabel(CarrierReturnLabelRequest request);
CarrierTransitTimeResult getTransitTime(CarrierTransitTimeRequest request);
List<CarrierServiceType> getAvailableServices(CarrierServiceAvailabilityRequest request);
}API mapping across carriers:
| Method | FedEx | USPS | UPS |
|---|---|---|---|
validateAddress() |
POST /address/v1/addresses/resolve |
GET /addresses/v3/address |
/api/addressvalidation/v1/ |
generateLabel() |
POST /ship/v1/shipments |
POST /labels/v3/label |
/api/shipments/v1/ |
voidLabel() |
PUT /ship/v1/shipments/cancel |
DELETE /labels/v3/label/{tracking} |
/api/shipments/v1/{id}/cancel |
trackPackage() |
POST /track/v1/trackingnumbers |
GET /tracking/v3/tracking/{tracking} |
/api/track/v1/details/{tracking} |
getRates() |
POST /rate/v1/rates/quotes |
POST /prices/v3/base-rates/search |
/api/rating/v1/ |
generateReturnLabel() |
POST /ship/v1/shipments/tag |
POST /labels/v3/return-label |
/api/shipments/v1/ (return flag) |
getTransitTime() |
POST /availability/v1/transittimes |
GET /service-standards/v3/standards |
/api/transit/v1/ |
getAvailableServices() |
POST /availability/v1/packageandserviceoptions |
Shipping Options API | Part of Rating API |
CarrierServiceRegistry
public class CarrierServiceRegistry
{
private static final CarrierServiceRegistry INSTANCE = new CarrierServiceRegistry();
private final ConcurrentHashMap<String, CarrierServiceProvider> providers = new ConcurrentHashMap<>();
public static CarrierServiceRegistry getInstance() { return INSTANCE; }
public void register(String carrierName, CarrierServiceProvider provider);
public Optional<CarrierServiceProvider> getProvider(String carrierName);
public Set<String> getRegisteredCarriers();
public void clear(); // for testing
}Common DTOs
CarrierAddress-- street, city, state, zip, countryCarrierAddressValidationRequest/CarrierAddressValidationResultCarrierLabelRequest/CarrierLabelResult/CarrierReturnLabelRequestCarrierRateRequest/CarrierRateResult-- normalized cost/service/transit daysCarrierTrackingResult-- tracking events, current status, estimated deliveryCarrierTransitTimeRequest/CarrierTransitTimeResultCarrierServiceAvailabilityRequestCarrierServiceType-- carrier + service code + descriptionCarrierPackageInfo-- dimensions, weightCarrierShipperInfo-- shipper address + account details (passed per-request)TrackingStatus-- enum: LABEL_CREATED, IN_TRANSIT, OUT_FOR_DELIVERY, DELIVERED, EXCEPTION, RETURNEDLabelFormat-- enum: ZPL, PDF, PNGCarrierApiException/CarrierValidationException
Design Notes
- Auth is transparent -- callers never manage tokens. Each implementation handles its own OAuth lifecycle.
TrackingStatusnormalizes carrier-specific event codes into a common enum.- FedEx supports batch tracking (30) and batch address validation (100). USPS/UPS are single-request. Implementations optimize internally.
LabelFormatenum on label requests abstracts carrier-specific format strings.
Dependencies
qqq-backend-core(QBitConfig, QBitProducer interfaces only)
Acceptance Criteria
- All interfaces and DTOs defined with Javadoc
- Registry follows QQQ singleton pattern (matches QSessionStoreRegistry)
- Unit tests for registry (register, resolve, clear, missing carrier)
- No carrier-specific code in this module
Child Issues
- feat: qqq-qbit-carrier-fedex -- FedEx REST API integration #391 -
qqq-qbit-carrier-fedex: FedEx REST API (MH-564) - feat: qqq-qbit-carrier-usps -- USPS REST API integration #392 -
qqq-qbit-carrier-usps: USPS REST API (MH-564) - feat: qqq-qbit-carrier-ups -- UPS REST API integration (future) #393 -
qqq-qbit-carrier-ups: UPS REST API (future)
Other Carriers Evaluated
| Carrier | Status | Auth | Notes |
|---|---|---|---|
| DHL Express | Deferred | Basic Auth (not OAuth2) | International-focused. MyDHL API at developer.dhl.com. Would need BasicAuth adapter instead of OAuth2. |
| Amazon Shipping | Skip | Complex OAuth (LWA) | Marketplace-specific, no address validation, no return labels. Not general-purpose. |
| OnTrac/LaserShip | Skip | Proprietary (not REST) | No public REST API. SOAP/XML only. Most integrators use EasyPost or ShipEngine aggregators. |
Motivation
Needed for MH-564 (carrier address validation + label generation in me-health-portal). Replaces legacy validation-combined.js SSH script with native Java QBit modules.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels