marain-dotnet / Marain.Workflow

Highly scalable workflow service. Sponsored by endjin.
GNU Affero General Public License v3.0
4 stars 1 forks source link

DSL for workflows #14

Open mwadams opened 4 years ago

mwadams commented 4 years ago

I'm proposing a simple DSL for workflows. Something along the following lines.

state WaitForOrder
    leave
        when OrderIsCreated
            when BelowApprovalThreshold
                try WaitForShipment
            else
                try WaitForApproval

state WaitForShipment
    on entry
        SendAcceptanceEmail
        CreateShippingRequest
    leave
        when ShipmentComplete
            try OrderComplete
        when ShipmentCancelled
            try OrderCancelled
        when OneWeekElapsed
            try WaitForShipment
                with SendShipmentReminder

state WaitForApproval
    on entry
        CreateApprovalRequest
    leave
        when Approved
            try WaitForShipment
        when Denied
            try OrderCancelled

state OrderComplete
    on entry
        CompleteWorkflow

state OrderCancelled
    on entry
        CancelOrder
        CompleteWorkflow

// **********************************               
// *********** CONDITIONS ***********
// **********************************               

condition OneWeekElapsed
    as ElapsedTimeCondition {
        1 week
    }

condition BelowApprovalThreshold
    as RulesCondition {
        let order = Http.getWithCache("/api/orders/{order.id}");
        let approvalThreshold = Configuration.get("OrderApprovalThreshold");
        if (order.price * order.quantity > approvalThreshold) {
            return false;
        }

        return true;
    }

condition OrderIsCreated
    as EntityIdCondition {
        EntityId matches order.id
        Activity is "Created" 
    }

condition ShipmentCancelled
    as EntityIdCondition {
        EntityId matches shipment.id
        Activity is "ShipmentCancelled" 
    }

condition Approved
    as EntityIdCondition {
        EntityId matches approval.id
        Activity is "Approved" 
    }

condition Denied
    as EntityIdCondition {
        EntityId matches approval.id
        Activity is "Denied" 
    }

condition ShipmentComplete
    as EntityIdCondition {
        EntityId matches shipment.id
        Activity is "ShipmentComplete" 
    }

// *******************************               
// *********** ACTIONS ***********
// *******************************               

action SendShipmentReminder
    as RulesAction {
        let shippingRequest = Http.getWithCache("/api/shipping/request/{shipment.id}");
        let routing = {
                from: "noreply@bigcorp.com",
                to: "shipping@bigcorp.com",
                subject: "No response to shipment request {shipment.id}"
            };
        Email.send(
            routing,
            "/email/shippingrequestremindertemplate",
            order)
    }

action SendAcceptanceEmail
    as RulesAction {
        let order = Http.getWithCache("/api/orders/{order.id}");
        let routing = {
                from: "noreply@bigcorp.com",
                to: order.customer.email,
                subject: "Order for {order.product} accepted"
            };
        Email.send(
            routing,
            "/email/orderacceptancetemplate",
            order)
    }

action CreateShippingRequest
    as RulesAction {
        let order = Http.getWithCache("/api/orders/{order.id}");
        let result = Http.post("/api/shipping/request", order);

        // add the shipment id to the instance context
        Workflow.updateCurrentInstance([
            { "shipment.id": result.id }
        ]);

        // create a new workflow instance for the shipment request 
        let shippingInstanceContext = {
            "request.id", result.id,
            "order.id", order.id
        };

        Workflow.createInstance("/orderworkflows/ordershipment", shippingInstanceContext);
    }

action CreateApprovalRequest
    as RulesAction {
        let order = Http.getWithCache("/api/orders/{order.id}");
        let result = Http.post("/api/approvals/request", order);

        // add the approval id to the instance context
        Workflow.updateCurrentInstance([
            { "approval.id": result.id }
        ]);

        // create a new workflow instance for the approval request 
        let approvalInstanceContext = {
            "approval.id", result.id,
            "order.id", order.id
        };

        Workflow.createInstance("/orderworkflows/orderapproval", approvalInstanceContext);
    }

action CancelOrder
    as RulesAction {         
        let result = Http.post("/api/order/cancel/{order.id}", { reason: "Shipping failed." });

        // create a new workflow instance for the cancellation workflow 
        let cancellationInstanceContext = {
            "cancellationrequest.id", result.id,
            "order.id", order.id
        };

        Workflow.createInstance("/orderworkflows/ordercancellation", cancellationInstanceContext);
    }

action CompleteWorkflow
    as RulesAction {
        Workflow.completeCurrentInstance();
    }
davidkpiano commented 4 years ago

Going to comment on this later, but just putting some resources/prior art here as food for thought: