Skip to content

Commit

Permalink
Merge pull request #2 from temporalio/order-base
Browse files Browse the repository at this point in the history
Simple Order and Shipment Workflows
  • Loading branch information
robholland authored Mar 25, 2024
2 parents 40a374a + b9e1f24 commit cf63eba
Show file tree
Hide file tree
Showing 11 changed files with 360 additions and 7 deletions.
6 changes: 1 addition & 5 deletions activities/activities.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
package activities

import "go.temporal.io/sdk/client"

type Activities struct {
client client.Client
}
type Activities struct{}
31 changes: 31 additions & 0 deletions activities/notification.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package activities

import (
"context"

"github.com/temporalio/orders-reference-app-go/pkg/ordersapi"
)

type ShipmentCreatedNotificationInput struct {
OrderID ordersapi.OrderID
}

func (a *Activities) ShipmentCreatedNotification(ctx context.Context, input ShipmentCreatedNotificationInput) error {
return nil
}

type ShipmentDispatchedNotificationInput struct {
OrderID ordersapi.OrderID
}

func (a *Activities) ShipmentDispatchedNotification(ctx context.Context, input ShipmentDispatchedNotificationInput) error {
return nil
}

type ShipmentDeliveredNotificationInput struct {
OrderID ordersapi.OrderID
}

func (a *Activities) ShipmentDeliveredNotification(ctx context.Context, input ShipmentDeliveredNotificationInput) error {
return nil
}
18 changes: 18 additions & 0 deletions activities/shipment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package activities

import (
"context"

"github.com/temporalio/orders-reference-app-go/pkg/ordersapi"
)

type RegisterShipmentInput struct {
OrderID ordersapi.OrderID
Items []ordersapi.Item
}

type RegisterShipmentResult struct{}

func (a *Activities) RegisterShipment(ctx context.Context, input RegisterShipmentInput) (RegisterShipmentResult, error) {
return RegisterShipmentResult{}, nil
}
20 changes: 20 additions & 0 deletions docs/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# System Overview

```mermaid
sequenceDiagram
participant Customer
participant Order
participant Shipment
participant Courier
Customer->>Order: place order
Order->>Shipment: create shipment
Shipment->>Courier: register shipment
Courier->>Shipment: shipment registered
Shipment->>Customer: shipment created
Courier->>Shipment: shipment dispatched
Shipment->>Customer: shipment dispatched
Courier->>Shipment: shipment delivered
Shipment->>Customer: shipment delivered
Order->>Customer: order complete
```
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ module github.com/temporalio/orders-reference-app-go

go 1.22

require go.temporal.io/sdk v1.26.0
require (
github.com/stretchr/testify v1.9.0
go.temporal.io/sdk v1.26.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
Expand All @@ -17,7 +20,6 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/robfig/cron v1.2.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.9.0 // indirect
go.temporal.io/api v1.29.1 // indirect
golang.org/x/exp v0.0.0-20231127185646-65229373498e // indirect
golang.org/x/net v0.22.0 // indirect
Expand Down
37 changes: 37 additions & 0 deletions internal/shipmentapi/shipmentapi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package shipmentapi

import (
"fmt"

"github.com/temporalio/orders-reference-app-go/pkg/ordersapi"
)

// The Shipment interfaces are only used internally.

// Signals will be sent by couriers via a local API service,
// so we don't need to expose these.

func ShipmentWorkflowID(orderID ordersapi.OrderID) string {
return fmt.Sprintf("shipment:%s", orderID)
}

type ShipmentInput struct {
OrderID ordersapi.OrderID
Items []ordersapi.Item
}

const ShipmentUpdateSignalName = "ShipmentUpdate"

type ShipmentStatus int

const (
ShipmentStatusRegistered ShipmentStatus = iota
ShipmentStatusDispatched
ShipmentStatusDelivered
)

type ShipmentUpdateSignal struct {
Status ShipmentStatus
}

type ShipmentResult struct{}
20 changes: 20 additions & 0 deletions pkg/ordersapi/ordersapi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package ordersapi

// The Orders API is exposed as the JSON equivalents will be used to start Orders via the local API.

// TODO: Do we want to do pass-through like this or separate and translate API definitions to Workflow types?
// TODO: Protobufs?

type OrderID string

type Item struct {
SKU string
Quantity int32
}

type OrderInput struct {
ID OrderID
Items []Item
}

type OrderResult struct{}
30 changes: 30 additions & 0 deletions workflows/order.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package workflows

import (
"github.com/temporalio/orders-reference-app-go/internal/shipmentapi"
"github.com/temporalio/orders-reference-app-go/pkg/ordersapi"
"go.temporal.io/sdk/workflow"
)

func Order(ctx workflow.Context, order ordersapi.OrderInput) (ordersapi.OrderResult, error) {
var result ordersapi.OrderResult

ctx = workflow.WithChildOptions(ctx,
workflow.ChildWorkflowOptions{
WorkflowID: shipmentapi.ShipmentWorkflowID(order.ID),
},
)

err := workflow.ExecuteChildWorkflow(ctx,
Shipment,
shipmentapi.ShipmentInput{
OrderID: order.ID,
Items: order.Items,
},
).Get(ctx, nil)
if err != nil {
return result, err
}

return result, nil
}
40 changes: 40 additions & 0 deletions workflows/order_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package workflows_test

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/temporalio/orders-reference-app-go/internal/shipmentapi"
"github.com/temporalio/orders-reference-app-go/pkg/ordersapi"
"github.com/temporalio/orders-reference-app-go/workflows"
"go.temporal.io/sdk/testsuite"
"go.temporal.io/sdk/workflow"
)

func TestOrderWorkflow(t *testing.T) {
s := testsuite.WorkflowTestSuite{}
env := s.NewTestWorkflowEnvironment()

env.RegisterWorkflow(workflows.Order)

orderInput := ordersapi.OrderInput{
Items: []ordersapi.Item{
{SKU: "test1", Quantity: 1},
{SKU: "test2", Quantity: 3},
},
}

env.OnWorkflow(workflows.Shipment, mock.Anything, mock.Anything).Return(func(ctx workflow.Context, input shipmentapi.ShipmentInput) (shipmentapi.ShipmentResult, error) {
return shipmentapi.ShipmentResult{}, nil
})

env.ExecuteWorkflow(
workflows.Order,
orderInput,
)

var result ordersapi.OrderResult
err := env.GetWorkflowResult(&result)
assert.NoError(t, err)
}
92 changes: 92 additions & 0 deletions workflows/shipment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package workflows

import (
"time"

"github.com/temporalio/orders-reference-app-go/activities"
"github.com/temporalio/orders-reference-app-go/internal/shipmentapi"
"go.temporal.io/sdk/workflow"
)

type shipment struct {
status shipmentapi.ShipmentStatus
}

func Shipment(ctx workflow.Context, input shipmentapi.ShipmentInput) (shipmentapi.ShipmentResult, error) {
return new(shipment).run(ctx, input)
}

func (s *shipment) run(ctx workflow.Context, input shipmentapi.ShipmentInput) (shipmentapi.ShipmentResult, error) {
workflow.Go(ctx, s.statusUpdater)

var result shipmentapi.ShipmentResult

ctx = workflow.WithActivityOptions(ctx,
workflow.ActivityOptions{
StartToCloseTimeout: 5 * time.Second,
},
)

err := workflow.ExecuteActivity(ctx,
a.RegisterShipment,
activities.RegisterShipmentInput{
OrderID: input.OrderID,
Items: input.Items,
},
).Get(ctx, nil)
if err != nil {
return result, err
}

err = workflow.ExecuteActivity(ctx,
a.ShipmentCreatedNotification,
activities.ShipmentCreatedNotificationInput{
OrderID: input.OrderID,
},
).Get(ctx, nil)
if err != nil {
return result, err
}

s.waitForStatus(ctx, shipmentapi.ShipmentStatusDispatched)

err = workflow.ExecuteActivity(ctx,
a.ShipmentDispatchedNotification,
activities.ShipmentDispatchedNotificationInput{
OrderID: input.OrderID,
},
).Get(ctx, nil)
if err != nil {
return result, err
}

s.waitForStatus(ctx, shipmentapi.ShipmentStatusDelivered)

err = workflow.ExecuteActivity(ctx,
a.ShipmentDeliveredNotification,
activities.ShipmentDeliveredNotificationInput{
OrderID: input.OrderID,
},
).Get(ctx, nil)
if err != nil {
return result, err
}

return result, nil
}

func (s *shipment) statusUpdater(ctx workflow.Context) {
var signal shipmentapi.ShipmentUpdateSignal

ch := workflow.GetSignalChannel(ctx, shipmentapi.ShipmentUpdateSignalName)
for {
ch.Receive(ctx, &signal)
s.status = signal.Status
}
}

func (s *shipment) waitForStatus(ctx workflow.Context, status shipmentapi.ShipmentStatus) {
workflow.Await(ctx, func() bool {
return s.status == status
})
}
67 changes: 67 additions & 0 deletions workflows/shipment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package workflows_test

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/temporalio/orders-reference-app-go/activities"
"github.com/temporalio/orders-reference-app-go/internal/shipmentapi"
"github.com/temporalio/orders-reference-app-go/pkg/ordersapi"
"github.com/temporalio/orders-reference-app-go/workflows"
"go.temporal.io/sdk/testsuite"
)

func TestShipmentWorkflow(t *testing.T) {
s := testsuite.WorkflowTestSuite{}
env := s.NewTestWorkflowEnvironment()
var a *activities.Activities

shipmentInput := shipmentapi.ShipmentInput{
OrderID: "test",
Items: []ordersapi.Item{
{SKU: "test1", Quantity: 1},
{SKU: "test2", Quantity: 3},
},
}

env.RegisterActivity(a.RegisterShipment)

env.OnActivity(a.ShipmentCreatedNotification, mock.Anything, mock.Anything).Return(
func(ctx context.Context, input activities.ShipmentCreatedNotificationInput) error {
env.SignalWorkflow(
shipmentapi.ShipmentUpdateSignalName,
shipmentapi.ShipmentUpdateSignal{
Status: shipmentapi.ShipmentStatusDispatched,
},
)

return nil
},
)

env.OnActivity(a.ShipmentDispatchedNotification, mock.Anything, mock.Anything).Return(
func(ctx context.Context, input activities.ShipmentDispatchedNotificationInput) error {
env.SignalWorkflow(
shipmentapi.ShipmentUpdateSignalName,
shipmentapi.ShipmentUpdateSignal{
Status: shipmentapi.ShipmentStatusDelivered,
},
)

return nil
},
)

env.RegisterActivity(a.ShipmentDeliveredNotification)

env.ExecuteWorkflow(
workflows.Shipment,
shipmentInput,
)

var result shipmentapi.ShipmentResult
err := env.GetWorkflowResult(&result)
assert.NoError(t, err)
}

0 comments on commit cf63eba

Please sign in to comment.