How to Integrate Temporal Workflow into Clean Architecture for Go Application?

I’m currently working on building a Go application following the Clean Architecture principles, and I’ve encountered a dilemma regarding the integration of Temporal into my architecture. Temporal seems to straddle multiple layers, and I’m unsure about the best approach to incorporate it effectively.

At first glance, it appears that Temporal should reside within the Delivery layer (Transport, Infrastructure, or Adapters), as it facilitates the execution of core business logic. However, Temporal also plays a crucial role in ensuring the safe execution of workflows, which is more aligned with the business logic itself. For instance, I can execute private workflows within Temporal, which are not intended for export to other services and do not utilize Protocol Buffers. These workflows can be triggered from various sources such as HTTP, Kafka, or gRPC, indicating a connection to the Delivery layer.

On the other hand, Temporal becomes necessary when exporting business logic to other services using Protocol Buffers, suggesting a closer association with the business logic layer. I’m struggling to determine the optimal organization of code and business logic in this scenario. Could anyone provide guidance on how to structure the code and interfaces to effectively integrate Temporal into a Clean Architecture for a Go application? As an example, I’ve attempted to define interfaces as follows:

type Temporal interface {
    PublicWorkflow(ctx workflow.Context, request *temporal.Request) (*pbtemporal.Response, error)
}

type UseCase interface {
    PrivateWorkflow(ctx workflow.Context, entity *entity.Entity) (*entity.Entity, error)
}

If I want to use PrivateWorkflow with Kafka, for instance, I’m uncertain whether to implement a temporalClient within Kafka (which may not be ideal) or create an adapter within UseCases. However, the latter approach could potentially violate the principle of keeping UseCases independent of delivery mechanisms.

Any insights or examples demonstrating the best practices for integrating Temporal workflows into a Clean Architecture for Go would be greatly appreciated.

Thank you!

I’m not familiar with the Clean Architecture, but a quick googling told me that it is about layering and separating business logic from implementation details.

Usually, these types of frameworks promise something that is not really possible. These abstractions are extremely leaky. For example, it is impossible to hide from the business logic that your code relies on persistence to save state and that the process that executes the business logic can crash at any time. So your business logic ends up being broken in a bunch of small functions that mostly perform DB transactions and implement some sort of state machine that can recover after crashes.

Temporal, on the other side, is based on the new concept of Durable Execution. This concept hides process crashes and eliminates the need to use persistence explicitly for most situations. So Temporal allows you to write your business logic directly without worrying about the leaky concerns other approaches like Cliean Architecture pose to developers.

Temporal still has to communicate with other systems that don’t use Temporal. This is done through activities. So they might contain a lot of lower-level details about the external world.

The best way to compare the approaches is to try to model and implement the real use case. I’m pretty sure that the Temporal way would be much more simple and practical.

My advice is to forget about Clean Architecture and just implement your use case using Temporal and then see for yourself if there are areas of the solution that you want to improve.

BTW. You don’t really need Kafka in the majority of cases when using Temporal.