Is it better to pass a specific “context” object to handlers rather than the entire domain object?

I’m designing a system where various “handlers” apply business rules to an object. Initially I had each handler receive the full domain object: // A large domain object with many properties and methods public class Order { private List items; private Customer customer; private Address billingAddress; private Address shippingAddress; private PaymentInfo payment; private DiscountRules discountRules; // … lots more fields and methods … } // Rule interface that takes the entire Order public interface Rule { void apply(Order order); } // Example handler only cares about items and customer status: public class LoyaltyDiscountRule implements Rule { @Override public void apply(Order order) { if (order.getCustomer().isGoldMember()) { order.applyDiscount(0.05); } } } Passing the whole Order felt too heavy—each rule only needs a couple of fields, yet has full access to mutate the order. Refactored approach I introduced a slim context object exposing only what a rule may need, plus an interface for allowed side-effects: // Exposes just the data each rule might use public class RuleContext { private final List items; private final Customer customer; private final OrderActions actions; public RuleContext(List items, Customer customer, OrderActions actions) { this.items = items; this.customer = customer; this.actions = actions; } public List getItems() { return items; } public Customer getCustomer() { return customer; } public OrderActions getActions() { return actions; } } // Only these modifications are allowed public interface OrderActions { void addDiscount(double rate); void markFreeShipping(); } Now each rule implements: public interface Rule { void apply(RuleContext ctx); } And a handler becomes: public class BulkPurchaseRule implements Rule { @Override public void apply(RuleContext ctx) { if (ctx.getItems().size() > 10) { ctx.getActions().addDiscount(0.10); } } } Questions Is this “Context + Actions” pattern a well-known or recommended design? What trade-offs does it introduce compared to passing the full domain object or individual parameters? Are there alternative patterns for limiting each handler’s access to only the data and mutations it needs?

May 22, 2025 - 18:40
 0

I’m designing a system where various “handlers” apply business rules to an object. Initially I had each handler receive the full domain object:

// A large domain object with many properties and methods
public class Order {
    private List items;
    private Customer customer;
    private Address billingAddress;
    private Address shippingAddress;
    private PaymentInfo payment;
    private DiscountRules discountRules;
    // … lots more fields and methods …
}

// Rule interface that takes the entire Order
public interface Rule {
    void apply(Order order);
}

// Example handler only cares about items and customer status:
public class LoyaltyDiscountRule implements Rule {
    @Override
    public void apply(Order order) {
        if (order.getCustomer().isGoldMember()) {
            order.applyDiscount(0.05);
        }
    }
}

Passing the whole Order felt too heavy—each rule only needs a couple of fields, yet has full access to mutate the order.

Refactored approach

I introduced a slim context object exposing only what a rule may need, plus an interface for allowed side-effects:

// Exposes just the data each rule might use
public class RuleContext {
    private final List items;
    private final Customer customer;
    private final OrderActions actions;

    public RuleContext(List items, Customer customer, OrderActions actions) {
        this.items = items;
        this.customer = customer;
        this.actions = actions;
    }

    public List getItems()        { return items;     }
    public Customer getCustomer()       { return customer;  }
    public OrderActions getActions()    { return actions;   }
}

// Only these modifications are allowed
public interface OrderActions {
    void addDiscount(double rate);
    void markFreeShipping();
}

Now each rule implements:

public interface Rule {
    void apply(RuleContext ctx);
}

And a handler becomes:

public class BulkPurchaseRule implements Rule {
    @Override
    public void apply(RuleContext ctx) {
        if (ctx.getItems().size() > 10) {
            ctx.getActions().addDiscount(0.10);
        }
    }
}

Questions

  1. Is this “Context + Actions” pattern a well-known or recommended design?
  2. What trade-offs does it introduce compared to passing the full domain object or individual parameters?
  3. Are there alternative patterns for limiting each handler’s access to only the data and mutations it needs?