Skip to content

DI: domain services, strategies, adapters

Besides repositories and EventBus, you can inject any interface into handlers by registering it with .bind(interface, impl) in the DomainModule.

Registering services and strategies

Use .bind() the same way as .repository(): the container will resolve the interface to the implementation and inject it into handler constructors by type.

pricing_module = (
    DomainModule("pricing")
    .bind(IDiscountStrategy, PercentDiscountStrategy)
    .bind(IPricingService, PricingServiceImpl)
    .command(CalculatePrice, CalculatePriceHandler)
    .query(GetPriceInfo, get_price_info_handler)
)

Define interfaces in the domain (or application) layer as Protocol or abstract base class; implement them in infrastructure or application. Handlers request the interface in the constructor:

class CalculatePriceHandler:
    def __init__(self, pricing_service: IPricingService):
        self._pricing = pricing_service

    def __call__(self, cmd: CalculatePrice) -> int:
        return self._pricing.compute_price_cents(cmd.amount_cents, cmd.discount_key)

The container resolves IPricingService to PricingServiceImpl; if that implementation depends on IDiscountStrategy, it will resolve to PercentDiscountStrategy as well.

When to use .bind()

  • Domain services — e.g. pricing, availability, tax calculation.
  • Strategies — e.g. discount algorithms, shipping cost rules.
  • Adapters — any port interface (not only Repository or EventBus) that the bounded context needs.

See the ecommerce example: pricing context with IPricingService, IDiscountStrategy, and .bind().