# Service tier best practices

The service layer encapsulates business operations and orchestrates data access for controllers, scheduled tasks, and other components. Keep it lean and testable by following these guidelines.

Services run in the same dependency injection scope as controllers and tasks. They work with domain entities and leave presentation concerns to higher layers.

## Keep services purposeful

* Only introduce a service when logic spans multiple repositories or requires cross‑cutting concerns like caching or messaging.
* Do not wrap a single repository call in a service method; expose the repository directly instead.

## Design clean interfaces

* Pair interfaces with implementations (e.g., `IProductService`/`ProductService`) so features can be swapped or mocked.
* Keep methods cohesive and asynchronous. Suffix async methods with `Async` and accept a `CancellationToken` for I/O operations.

## Minimize dependencies

* Inject only required collaborators through the constructor and avoid the service locator pattern.
* Never inject controllers, Razor helpers, or other presentation types.
* Prefer working with domain models and repositories from `SmartDbContext`.

## Avoid service chains

* Services should be stateless and independent. When one service needs functionality from another, extract a shared helper or domain method.
* Fetch data in batches rather than looping over items and calling another service per item.

## Example implementation

A minimal service reveals the interface, uses asynchronous data access, caching, and the optional logger property:

```csharp
public interface IPriceService
{
    Task<decimal> GetPriceAsync(int productId, CancellationToken cancelToken = default);
}

public class PriceService : IPriceService
{
    private readonly IRepository<Product> _productRepo;
    private readonly ICache _cache;

    public ILogger Logger { get; set; } = NullLogger.Instance;

    public PriceService(IRepository<Product> productRepo, ICache cache)
    {
        _productRepo = productRepo;
        _cache = cache;
    }

    public async Task<decimal> GetPriceAsync(int productId, CancellationToken cancelToken = default)
    {
        var cacheKey = $"product-price-{productId}";
        return await _cache.GetAsync(cacheKey, async () =>
        {
            var product = await _productRepo.GetByIdAsync(productId, cancelToken);
            return product.Price;
        });
    }
}
```

## Logging and caching

* Expose an optional logger so the container can inject a contextual instance:

  ```csharp
  public ILogger Logger { get; set; } = NullLogger.Instance;
  ```
* Use `ICache` for expensive queries and invalidate entries when underlying data changes.

## Testing

* Keep interfaces small to simplify mocking and unit testing.
* Avoid static state unless it is thread‑safe and intentionally shared.


---

# Agent Instructions: 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:

```
GET https://dev.smartstore.com/framework/advanced/service-tier-best-practices.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
