1

To simplify the domain let's say we have a system that takes in orders from customers. There's an API endpoint that validates the input, and if valid pushes the order onto a messaging queue to be picked up by an event handler to process the orders. So a simple (.NET) API controller might look like this:

[HttpPost]
public Task<IActionResult> CreateOrder(CreateOrderRequest request)
{
    ValidateInput(request);

    // Some other checks

    _eventPublisher.Publish(createOrderEvent);
}

Another system exists that we want to interact with which cares about all orders that are created. Due to this system being new a feature flag should be introduced that can be enabled or disabled on deployed environments. Simple enough, we could just introduce a flag in configuration. We could introduce a simple if check here in the controller, but we may want to check for this in multiple places (like the event handler).

So I created a service type to communicate with the external system and introduced a factory for the feature flag. The factory returns the real service if the feature is turned on, or a fake one if it is not. So here's what that looks like:

public interface IExternalOrderSystemService
{
    Task CreateOrder(CreateExternalOrderRequest request);
    Task ConfirmOrder(ConfirmExternalOrderRequest request); // Used for when payment is approved
}

public class ExternalOrderSystemServiceFactory
{
    // Dependencies

    public IExternalOrderSystemService CreateService()
    {
        if (_settings.Enabled)
            return new RealExternalOrderSystemService();
        
        return new FakeExternalOrderSystemService();
    }
}

// Update to the controller
public Task<IActionResult> CreateOrder(CreateOrderRequest request)
{
    ValidateInput(request);

    // Some other checks, creation of external request and event

    var externalOrderService = _factory.CreateService();
    externalOrderService.CreateOrder(externalRequest);
    _eventPublisher.Publish(createOrderEvent);
}

But now there's an additional change which is where I'm getting a bit stuck. Now, if the feature flag is enabled the order should not be pumped into the event publisher at all, but only the external system should be called. If the feature is disabled then the order should go through the publisher as it does today. Not only that, if the order is of a specific type (let's just say Order Type A to keep things simple) then the order should go through the event publisher even if the feature is enabled.

if (_settings.Enabled && request.OrderType != OrderType.A)
    // Send to external system via HTTP
else
    // Send order through message bus pipeline

So what I began to do was start creating a type that can essentially route the order through one method or another. I'm not great with names... I called it an order distributor. The issue that I'm having is that the logic for determining whether it's "enabled" or not is duplicated between the service factory and this new distributor. I have to keep it in both because of the ConfirmOrder method that is called later on in the process. Both of these types must know about the feature and order type check for toggling.

public class OrderDistributor
{
    public Task Send(CreateOrderRequest request)
    {
        if (_settings.Enabled && request.OrderType != OrderType.A)
        {
            var service = _factory.CreateService();
            // Map to external request
            service.CreateOrder(externalRequest);
        }
        else
        {
            // Create event
            _eventPublisher.Publish(orderEvent);
        }
    }
}

So my thought for solving this would be to create some sort of togglign strategy class that can be consumed by both and called in both areas. By doing so I keep the logic in one place.

public interface IOrderToggleStrategy
{
    bool IsEnabled(OrderType orderType);
}

But I'm starting to wonder whether this all makes sense. Is it starting to look like spaghetti? This is as of right now the best design that I've been able to come up with, but I'm open to alternative ideas and for suggestions from anyone who has solved a similar problem in the past.

3 Answers 3

3

Orthogonality is the key.

The information whether an order should go through the event publisher or only to an external system (or both?) should be kept orthogonal to your feature flag. For example, this information could be a property of an Order object and initialized at construction time, with different logic for FakeExternalOrderSystemService and RealExternalOrderSystemService.

Then the order distributor can ask the object how to process it, without knowing anything about the feature flag.

2
  • To be honest I’m not sure I want the Order object to know about the external system. At the moment nothing in the order or order processing has needed to change apart from the external service. I’d like to keep it that way if possible
    – Serberuss
    Aug 30, 2022 at 6:04
  • @Serberuss: I think you did not get my point in full: the order does not necessarily need to "know about the external system". But it needs to have some property which allows "someone else" (in your example the OrderDistributor) to decide how to route it, without knowing directly about the feature flag. I cannot tell you a good name for that property since I don't know your real system, but I am sure you can if you just try.
    – Doc Brown
    Aug 30, 2022 at 6:15
1

If you want to do complex feature flags then you need to move away from the idea that they are part of the config which is turned off or on.

Instead think about the problem in terms of "I want this group of customers to get the new order process". Where the group of customers is determined by whatever factor marketing think up that week.

Once you are thinking in these terms it becomes obvious that your site/services need to be able to process orders in several different ways depending on the meta data associated with a particular operation. In this case "is the customer in the test group".

This simplifies the logic in your application. As long as the meta data for customer group is available at each stage of your order processing you can determine what logic needs to be implemented at that stage.

You of course need some external process to determine whether the customer falls into the test group or not, but this can be outsourced to a separate API call, or as in docs answer, calculated at the start and persisted with the order.

This pattern works well for trailing new features, but its not really the same as "this feature isn't ready yet, I want to check it into the codebase but have it disabled". Your new process is part of the application and at least must be regression tested to make sure you haven't broken anything by adding the new path into the code.

0

Building up on @DocBrown's idea, if adding a property in Order object isn't suitable, maybe the CreateOrder method can return let's say a boolean that indicates whether event needs to published or not.

Though one can argue that's adding additional logic in CreateOrder which at first glance may not even make sense by just looking at the function; what is this boolean being returned for?. So, I suggest creating another function ShouldPublishEvent that accepts as parameters IExternalOrderSystemService and Order type, validates the conditions such as if the service is of type FakeExternalOrderSystemService or Order type is A then return true. If true then go ahead and publish the event.

I think the latter is better because it translates your requirements as they are in code - it's more readable and maintainable in my opinion - and doesn't tie the logic of publishing the event to the feature flag which you wanted to avoid because of duplication if I understood correctly

Hope it helps

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.