Skip to content

feat: carrier service QBit hierarchy (qqq-qbits-carrier) #389

@KofTwentyTwo

Description

@KofTwentyTwo

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 CarrierServiceProvider interface
  • 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-core for 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, country
  • CarrierAddressValidationRequest / CarrierAddressValidationResult
  • CarrierLabelRequest / CarrierLabelResult / CarrierReturnLabelRequest
  • CarrierRateRequest / CarrierRateResult -- normalized cost/service/transit days
  • CarrierTrackingResult -- tracking events, current status, estimated delivery
  • CarrierTransitTimeRequest / CarrierTransitTimeResult
  • CarrierServiceAvailabilityRequest
  • CarrierServiceType -- carrier + service code + description
  • CarrierPackageInfo -- dimensions, weight
  • CarrierShipperInfo -- shipper address + account details (passed per-request)
  • TrackingStatus -- enum: LABEL_CREATED, IN_TRANSIT, OUT_FOR_DELIVERY, DELIVERED, EXCEPTION, RETURNED
  • LabelFormat -- enum: ZPL, PDF, PNG
  • CarrierApiException / CarrierValidationException

Design Notes

  • Auth is transparent -- callers never manage tokens. Each implementation handles its own OAuth lifecycle.
  • TrackingStatus normalizes 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.
  • LabelFormat enum 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

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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions