home / skills / kaakati / rails-enterprise-dev / error-handling

This skill explains error handling patterns using exception, failure, and Either types to create robust, scalable error flows.

npx playbooks add skill kaakati/rails-enterprise-dev --skill error-handling

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
1.9 KB
---
name: "Error Handling Patterns"
description: "Exception classes, failure classes, Either type, and error handling strategies"
version: "1.0.0"
---

# Error Handling Patterns

## Exception Classes (Data Layer)

```dart
abstract class AppException implements Exception {
  final String message;
  final String? code;
  
  const AppException({required this.message, this.code});
}

class ServerException extends AppException {
  final int? statusCode;
  
  const ServerException({
    required super.message,
    super.code,
    this.statusCode,
  });
}

class CacheException extends AppException {
  const CacheException({required super.message, super.code});
}

class NetworkException extends AppException {
  const NetworkException({
    super.message = 'No internet connection',
    super.code,
  });
}
```

## Failure Classes (Domain Layer)

```dart
import 'package:equatable/equatable.dart';

abstract class Failure extends Equatable {
  final String message;
  
  const Failure(this.message);
  
  @override
  List<Object> get props => [message];
}

class ServerFailure extends Failure {
  const ServerFailure(super.message);
}

class CacheFailure extends Failure {
  const CacheFailure(super.message);
}

class NetworkFailure extends Failure {
  const NetworkFailure([super.message = 'No internet connection']);
}
```

## Either Type Usage

```dart
import 'package:dartz/dartz.dart';

// Repository returns Either<Failure, Entity>
Future<Either<Failure, User>> getUser(String id) async {
  try {
    final model = await provider.fetchUser(id);
    return Right(model.toEntity());
  } on ServerException catch (e) {
    return Left(ServerFailure(e.message));
  } on NetworkException {
    return Left(NetworkFailure());
  }
}

// Controller handles Either result
final result = await getUserUseCase('123');
result.fold(
  (failure) => _handleError(failure),
  (user) => _user.value = user,
);
```

Overview

This skill catalogs robust error handling patterns for enterprise Rails workflows adapted from layered application design. It describes exception classes at the data layer, failure classes at the domain layer, and how to use an Either-style result type to propagate and handle errors predictably. The goal is to make error flows explicit, testable, and portable across services and agents.

How this skill works

Define concrete exception types where errors originate (e.g., ServerException, CacheException, NetworkException) so low-level code can throw meaningful errors. Map those exceptions to domain-level Failure objects (e.g., ServerFailure, CacheFailure, NetworkFailure) inside repositories. Return results as Either<Failure, Entity> so callers explicitly handle success and failure paths without throwing. Controllers or use cases fold the Either to handle failures centrally and update application state on success.

When to use it

  • When you need clear separation between transport errors and business failures.
  • In repositories or adapters that call external services, caches, or databases.
  • When you want predictable, testable error propagation across agents or services.
  • When orchestrating multi-agent workflows that require centralized failure tracking.
  • When you want to avoid exceptions crossing domain boundaries and causing uncontrolled crashes.

Best practices

  • Keep exceptions confined to the layer where they originate; convert to Failure objects at the boundary.
  • Use explicit Failure subclasses to convey intent and allow pattern matching or type checks.
  • Return Either types from async operations rather than throwing for normal failure scenarios.
  • Handle network/no-connection as a distinct failure with user-friendly messaging.
  • Centralize error-to-user mapping in controllers or a dedicated error handler to keep use cases pure.

Example use cases

  • A repository fetching data from an API catches ServerException and returns ServerFailure via Either.
  • A background worker interacting with cache converts CacheException to CacheFailure and retries or records metrics.
  • A multi-agent orchestration layer folds repository Eithers to route tasks to compensating actions on failure.
  • A UI controller folds an Either to show an error banner or update state with the fetched entity.

FAQ

Why convert exceptions to Failure objects?

To decouple low-level error details from business logic, make errors explicit in return types, and allow functional composition without throwing.

When should I still throw exceptions?

Reserve throws for unrecoverable conditions or programming errors. Use Failures for expected operational errors you want callers to handle.