Context

Context

Context allows services to be run within the same execution scope, enabling shared state and coordinated transactions.

Key Features

  • Services share arguments marked as context: true

  • If any service fails, the entire context fails and rolls back database changes

How to Run Services in the Same Context

To run a service in the same context, call with(self) before the #run method.

Context Rollback

Example:

Let's say we have two services: User::Create and Profile::Create. We want to ensure that if either service fails, all database changes are rolled back.

class User::Create < ApplicationService
  # Arguments
  arg :attributes, type: Hash

  # Steps
  step :create_user
  step :create_profile
  step :send_welcome_email

  # Outputs
  output :user, type: User
  output :profile, type: Profile

  def create_user
    self.user = User.create!(attributes)
  end

  def create_profile
    service = Profile::Create
      .with(self) # This runs the service in the same context
      .run(user:)

    self.profile = service.profile
  end

  # If the Profile::Create service fails, this step and any following steps won't execute
  # And all database changes will be rolled back
  def send_welcome_email
    # We don't run this service in the same context
    # Because we don't care too much if it fails
    service = Mailer::SendWelcomeEmail.run(user:)

    # Handle the failure manually if needed
    if service.failed?
      # Handle the failure
    end
  end
end

Context Arguments

Context arguments are shared between services running in the same context. This can make them a bit less predictable and harder to test.

It's recommended to use context arguments only when necessary and keep them as close to the root service as possible. For example, you can use them to share current_user or current_organization between services.

What's Next?

The next step is to learn about error handling in Operandi.

Next: Errors

Last updated