Best way to design a Subscription Workflow

Hello,

I’m developing a subscription management system and would like some advice on how best to implement the following case:

The user has a subscription to a specific Plan. The plan comes at a price. Once a month the subscription money is debited.

Here is some simplified code:

func SubscriptionWorkflow(ctx workflow.Context, subscriptionID string) error {
	var subscription Subscription
	var plan Plan

	for {
		workflow.ExecuteActivity(ctx, activities.GetSubscriptionAndPlan, subscriptionID).Get(ctx, &subscription, &plan)

		workflow.ExecuteActivity(ctx, activities.ChargeMoney, plan.Price).Get(ctx, nil)

		nextBillingDate := subscription.nextBillingDate.AddDate(0, 1, 0) 

		workflow.ExecuteActivity(ctx, activities.UpdateSubscriptionNextBillingDate, subscription.ID, nextBillingDate).Get(ctx, nil)

		_ = workflow.Sleep(ctx, time.Until(nextBillingDate))
	}
}

But I need to be able to change my subscription plan. It’s not an easy operation. I may need to charge additional money, and I may need to update the DB record. I’m thinking of using Workflow for this. Let’s say I have such a ChangePlanWorkflow written. I have the following limitations:

  1. I need to be able to run the ChangePlanWorkflow immediately on request, rather than waiting for the next subscription debit to occur
  2. I need to pause the main SubscriptionWorkflow loop while ChangePlanWorkflow is running. And after ChangePlanWorkflow is finished, I want to continue the subscription debit with the new plan.

I have a few thoughts on how to do this, but not sure how best to do it

  1. Run ChangePlanWorkflow as a child workflow from SubscriptionWorkflow by signal or run it as a separate workflow outside of ChangePlanWorkflow and check its activity at the database record level?
  2. Need to realize that ChangePlanWorkflow has terminated and resume the subscription cycle. Should I do a regular polling in the loop once a minute?

I will be glad for any help and approximate realizations

You can use AwaitWithTimeout:

	for {
		workflow.ExecuteActivity(ctx, activities.GetSubscriptionAndPlan, subscriptionID).Get(ctx, &subscription, &plan)

		workflow.ExecuteActivity(ctx, activities.ChargeMoney, plan.Price).Get(ctx, nil)

		nextBillingDate := subscription.nextBillingDate.AddDate(0, 1, 0)

		workflow.ExecuteActivity(ctx, activities.UpdateSubscriptionNextBillingDate, subscription.ID, nextBillingDate).Get(ctx, nil)

		cancelChannel := workflow.GetSignalChannel(ctx, "cancel")
		workflow.AwaitWithTimeout(ctx,
			time.Until(nextBillingDate), func() bool {
				return cancelChannel.Len() > 0
			})
		if cancelChannel.Len() > 0 {
			// execute cancellation child workflow
			break
		}
	}

Note that you cannot have an infinite loop inside a workflow, as history size should be bounded. You have to call continue-as-new. I recommend calling it at least once a month to ensure you can update the code more easily.

Thanks! For some reason this is not described in the documentation Temporal Go SDK development documentation | Temporal Documentation