Core Service Patterns
Este documento define os padrões obrigatórios para todos os serviços no Core layer.
Princípios Fundamentais
- Core não depende de camadas superiores - Apenas System libraries e outros módulos Core
- Interfaces para tudo - Todo serviço público tem uma interface
- Error handling consistente - Result
para operações de negócio - Logging obrigatório - ILogger injetado em todos os serviços
- Thread-safety quando necessário - Documentar e implementar quando compartilhado
Anatomia de um Serviço Core
Template Básico
namespace Core.{Module};
using Core.Common;
using Core.Logging;
/// <summary>
/// Interface pública do serviço
/// </summary>
public interface IMyService
{
Result<TOutput> DoOperation(TInput input);
}
/// <summary>
/// Implementação do serviço
/// </summary>
public class MyService : IMyService
{
private readonly ILogger _logger;
private readonly IDependency _dependency;
public MyService(ILogger logger, IDependency dependency)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_dependency = dependency ?? throw new ArgumentNullException(nameof(dependency));
}
public Result<TOutput> DoOperation(TInput input)
{
// 1. Validar entrada
if (input == null)
return Result<TOutput>.Failure("Input cannot be null");
// 2. Logar início
_logger.LogDebug($"Starting operation with input: {input}");
try
{
// 3. Executar lógica
var result = PerformLogic(input);
// 4. Logar sucesso
_logger.LogDebug($"Operation completed successfully");
return Result<TOutput>.Success(result);
}
catch (Exception ex)
{
// 5. Logar erro e retornar falha
_logger.LogError($"Operation failed: {ex.Message}", ex);
return Result<TOutput>.Failure($"Operation failed: {ex.Message}");
}
}
private TOutput PerformLogic(TInput input)
{
// Lógica interna pode lançar exceções
// Serão capturadas pelo método público
return default!;
}
}
Padrão de Error Handling
Quando usar Result
USE Result
- Operações de negócio que podem falhar previsivelmente
- Validações de entrada
- Operações que dependem de estado externo (arquivo, rede, etc)
- Qualquer operação onde falha é um cenário esperado
Exemplos:
Result<CombatState> StartCombat(IReadOnlyList<CombatParticipantDefinition> participants);
Result<ActionDefinition> GetDefinition(string actionId);
Result<float> Evaluate(ExpressionEvaluationRequest request);
Quando lançar exceções
LANCE EXCEÇÕES para:
- Violações de contrato (ArgumentNullException, ArgumentException)
- Bugs de programação (InvalidOperationException para estados impossíveis)
- Falhas irrecuperáveis do sistema
Exemplos:
if (logger == null)
throw new ArgumentNullException(nameof(logger));
if (_isDisposed)
throw new ObjectDisposedException(nameof(MyService));
NUNCA engula exceções
ERRADO:
catch (Exception ex)
{
_logger.LogError($"Error: {ex.Message}");
return defaultValue; // ❌ Silencioso
}
CORRETO:
catch (Exception ex)
{
_logger.LogError($"Error: {ex.Message}", ex);
return Result<T>.Failure($"Operation failed: {ex.Message}");
}
Padrão de Logging
Níveis de Log
- LogDebug: Fluxo detalhado de execução (início/fim de operações)
- LogInformation: Eventos importantes do sistema (combate iniciado, config carregada)
- LogWarning: Situações anormais mas recuperáveis
- LogError: Erros que impedem operação de completar
Exemplos
// Início de operação
_logger.LogDebug($"Starting combat with {participants.Count} actors");
// Evento importante
_logger.LogInformation($"Combat {combatId} started successfully");
// Situação anormal
_logger.LogWarning($"Action {actionId} not found, using default");
// Erro
_logger.LogError($"Failed to load config: {ex.Message}", ex);
Padrão de Validação
Validação de Construtor
public MyService(ILogger logger, IDependency dependency)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_dependency = dependency ?? throw new ArgumentNullException(nameof(dependency));
}
Validação de Entrada
public Result<T> DoOperation(string input)
{
if (string.IsNullOrWhiteSpace(input))
return Result<T>.Failure("Input cannot be empty");
if (input.Length > 100)
return Result<T>.Failure("Input too long (max 100 characters)");
// Continuar com lógica
}
Padrão de Thread-Safety
Quando necessário
- Serviços registrados como Singleton no DI
- Serviços que mantêm estado mutável compartilhado
- Caches e dicionários compartilhados
Implementação
public class ThreadSafeService
{
private readonly Dictionary<string, T> _cache = new();
private readonly object _lock = new();
public T GetOrAdd(string key, Func<T> factory)
{
lock (_lock)
{
if (_cache.TryGetValue(key, out var value))
return value;
var newValue = factory();
_cache[key] = newValue;
return newValue;
}
}
}
Alternativa: Use ConcurrentDictionary<TKey, TValue> quando apropriado.
Checklist de Novo Serviço
Antes de criar um novo serviço no Core, verifique:
- Tem interface pública (
I{ServiceName}) - Construtor valida todas as dependências (null checks)
- Injeta
ILoggervia construtor - Métodos públicos retornam
Result<T>para operações que podem falhar - Exceções são lançadas apenas para bugs/violações de contrato
- Logging adequado (início de operações, erros, eventos importantes)
- Thread-safe se registrado como Singleton
- Testes unitários cobrem casos de sucesso e erro
- Documentação XML em interface pública
Exemplos de Serviços Bem Implementados
Bons Exemplos no Core
ActionManager- Validação completa, Result, logging EventBus- Thread-safe, logging, error handlingResourceManager- Result, validação, logging PipelineManager- Thread-safe, lazy initialization
Serviços que Precisam de Melhoria
ActionAffordabilityService- Falta logging, validação, ResultEntityFactory- Falta validação, logging, ResultMathEngine- Lança muitas exceções raw, falta Result
Anti-Patterns a Evitar
❌ Estado Global Mutável
public static class BadService
{
private static Dictionary<string, T> _cache = new(); // ❌ Global mutável
}
❌ Retornar Null
public OperationMetadata? GetOperation(string name)
{
return _operations.FirstOrDefault(op => op.Name == name); // ❌ Null
}
Correto:
public Result<OperationMetadata> GetOperation(string name)
{
var operation = _operations.FirstOrDefault(op => op.Name == name);
return operation != null
? Result<OperationMetadata>.Success(operation)
: Result<OperationMetadata>.Failure($"Operation '{name}' not found");
}
❌ Engolir Exceções
catch (Exception ex)
{
_logger.LogError(ex.Message);
return context; // ❌ Continua como se nada tivesse acontecido
}
❌ Utilitários Estáticos com Dependências
public static class FileHelper
{
public static T ReadJson<T>(string path) // ❌ Não testável
{
return JsonSerializer.Deserialize<T>(File.ReadAllText(path));
}
}
Correto:
public interface IFileHelper
{
Result<T> ReadJson<T>(string path);
}
public class FileHelper : IFileHelper
{
private readonly ILogger _logger;
public FileHelper(ILogger logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public Result<T> ReadJson<T>(string path)
{
try
{
var content = File.ReadAllText(path);
var result = JsonSerializer.Deserialize<T>(content);
return Result<T>.Success(result!);
}
catch (Exception ex)
{
_logger.LogError($"Failed to read JSON from {path}: {ex.Message}", ex);
return Result<T>.Failure($"Failed to read JSON: {ex.Message}");
}
}
}
Referências
Core.Common.Result<T>- Tipo de retorno para operações que podem falharCore.Logging.ILogger- Interface de loggingCore.Events.IEventBus- Sistema de eventosarquitetura-engine.md- Visão geral da arquitetura