Efficient Workflow in Go with Temporal.io: Repository Pattern

Younis Jad
Lyonas
Published in
3 min readJul 6, 2023

TDLR: This pattern involves creating a dedicated repository layer for data access, which abstracts the data access layer from the rest of the application.

In previous Article about Layered Architecture we discussed the implementation theory and example in Go + Temporal.io, The Repository Pattern and Layered Architecture are related but distinct patterns that can be used to structure the code of an application. While they both aim to separate concerns and improve code maintainability, they do so in different ways.

to implement event based systems check out this tutorial: Using Temporal to Build Scalable and Fault-Tolerant Applications in Golang

Repository Pattern Architicture

This pattern involves creating a dedicated repository layer for data access, which abstracts the data access layer from the rest of the application.

Structure

main.go
config/
config.go
repository/
database.go
example_repository.go
service/
service.go
workflow/
example_workflow.go
activity/
example_activity.go

The main.go file is the entry point of the application. It imports the necessary packages and starts the workflow.

The config package contains a config.go file that reads configuration values from a file or environment variables to set up the application.

The repository package contains a database.go file that interacts with the database to retrieve or store data. We also have an example_repository.go file that implements the repository pattern to abstract the data access layer from the business logic layer.

The service package contains a service.go file that encapsulates complex business logic and interacts with the database through the repository layer.

The workflow package contains a example_workflow.go file that defines the main workflow logic for Temporal.io engine.

The activity package contains a example_activity.go file that defines the activity logic executed by Temporal.io engine.

Here’s an example of the ExampleWorkflow and ExampleActivity functions in example_workflow.go and example_activity.go respectively:

// example_workflow.go
func ExampleWorkflow(ctx workflow.Context) error {
var data []string
err := workflow.ExecuteActivity(ctx, ExampleActivity, "example").Get(ctx, &data)
if err != nil {
return err
}

// pass the data through the service layer
service := NewExampleService(NewExampleRepository())
result, err := service.DoSomeComplexBusinessLogic(data)
if err != nil {
return err
}

// return the result to the caller
return workflow.SetResult(ctx, result)
}
// example_activity.go
func ExampleActivity(ctx context.Context, input string) ([]string, error) {
// Try to retrieve data 3 times with 2 seconds between each retry in case of any error
retryOptions := temporal.RetryOptions{
InitialInterval: time.Second * 2,
BackoffCoefficient: 2.0,
MaximumAttempts: 3,
DoNotRetry: []error{ErrNonRetryable},
NonRetryableErrorTypes: []reflect.Type{reflect.TypeOf(ErrNonRetryable)},
}

// Use the repository to retrieve data from the database
repo := NewExampleRepository()

data, err := repo.GetDataFromDatabaseByKey(ctx, key)
if err != nil {
return nil, temporal.NewContinueAsNewError(ctx, ExampleActivity, input)
}
return data, nil
}

These functions demonstrate how the repository pattern works with Temporal.io engine to manage workflows and activities. The ExampleActivity function retries the RetrieveDataFromDatabase function in case of any error with backoff exponential, and pass the retrieved data through the service layer to do business logic. When business logic is executed without any error, the ExampleWorkflow function uses the Temporal.io engine to set the result of the workflow.

By structuring the application in this way, we can keep the data access and business logic layers decoupled, making it easier to maintain, manage and test the application.

--

--

Younis Jad
Lyonas
Editor for

Tech Evangelist, Experienced software engineer, Passionate about learning and building innovative, scalable solutions.