> For the complete documentation index, see [llms.txt](https://light-services.kodkod.me/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://light-services.kodkod.me/examples/best-practices.md).

# Best Practices

This guide explores best practices for building applications with Operandi, keeping things simple and effective.

## Create Top-Level Services

Creating top-level services for your application is highly recommended. This approach helps keep your services small and focused on a single task.

### Application Service

`ApplicationService` serves as the base class for all services in your application. Use it to place common methods, helpers, context arguments, etc. Remember, it should not contain any business logic.

### Create, Update, and Destroy Services

Since create, update, and destroy are fundamental operations in any application, having dedicated services for them is a good idea. This keeps important tasks like authorization, data sanitization, and WebSocket broadcasts close to the core of your application.

* `CreateRecordService` - for creating records
* `UpdateRecordService` - for updating records
* `DestroyRecordService` - for destroying records

Think of these services as wrappers around the `ActiveRecord::Base#create`, `#update`, and `#destroy` methods.

### Read Services

Similar to the above services but focused on finding records. Use these for generic authorization, filtering, sorting, pagination, etc.

* `FindRecordService` - for finding a single record
* `FindAllRecordsService` - for finding multiple records

## Avoid Defining Context Arguments Outside Top-Level Services

Using context arguments outside of top-level services can make your services less modular and more unpredictable. Keep them within the core services for better modularity.

## Keep Services Small

Aim to keep your services small and focused on a single task. Ideally, a service should have no more than 3-5 steps. If a service has more steps, consider splitting it into multiple services.

## Passing Arguments from Controllers

It's a good practice to create a wrapper method to extend arguments passed to the service from the controller.

Consider this example controller:

```ruby
class PostsController < ApplicationController
  def index
    service = Post::FindAll.run(current_user:, current_organization:)
    render json: service.posts
  end

  def create
    service = Post::Create.run(attributes: params[:post], current_user:, current_organization:)

    if service.success?
      render json: service.post
    else
      render json: { errors: service.errors }, status: :unprocessable_entity
    end
  end

  def unpublish
    service = Post::Unpublish.run(id: params[:id], current_user:, current_organization:)

    if service.success?
      render json: service.post
    else
      render json: { errors: service.errors }, status: :unprocessable_entity
    end
  end

  # ...
end
```

Manually passing `current_user` and `current_organization` each time can be cumbersome. Let's simplify it with a helper method in our `ApplicationController`:

```ruby
class ApplicationController < ActionController::API
  private

  def service_args(hash = {})
    hash.reverse_merge(
      current_user:,
      current_organization:,
    )
  end
end
```

Now we can refactor our controller:

```ruby
class PostsController < ApplicationController
  def index
    service = Post::FindAll.run(service_args)
    render json: service.posts
  end

  def create
    service = Post::Create.run(service_args(attributes: params[:post]))

    if service.success?
      render json: service.post
    else
      render json: { errors: service.errors }, status: :unprocessable_entity
    end
  end

  def unpublish
    service = Post::Unpublish.run(service_args(id: params[:id]))

    if service.success?
      render json: service.post
    else
      render json: { errors: service.errors }, status: :unprocessable_entity
    end
  end

  # ...
end
```

With this setup, adding a new top-level context argument only requires a change to the `service_args` method in `ApplicationController`.

## Use Concerns

If you have common logic that you want to share between services, use concerns. Avoid putting too much logic into your `ApplicationService` class; it's better to split it into concerns.

For example, create an `AuthorizeUser` concern for authorization logic.

```ruby
# app/services/concerns/authorize_user.rb
module AuthorizeUser
  extend ActiveSupport::Concern

  included do
    # ...
  end
end
```

```ruby
# app/services/application_service.rb
class ApplicationService < Operandi::Base
  include AuthorizeUser
end
```

## What's Next?

Explore practical recipes for common patterns:

[Next: Recipes](/examples/recipes.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://light-services.kodkod.me/examples/best-practices.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
