Arquitetura Modular do Sloth Runner¶
Visão Geral¶
O Sloth Runner foi refatorado para seguir princípios de design modular e aplicar design patterns reconhecidos pela indústria. Isso torna o código mais fácil de manter, testar e estender.
Estrutura de Diretórios¶
cmd/sloth-runner/
├── commands/ # Comandos Cobra organizados
│ ├── context.go # AppContext (Dependency Injection)
│ ├── root.go # Root command
│ ├── version.go # Version command
│ ├── run.go # Run command
│ ├── agent/ # Comandos do agente
│ ├── stack/ # Comandos de stack
│ └── scheduler/ # Comandos de scheduler
├── handlers/ # Business logic handlers
│ └── run_handler.go # Handler para comando run
├── services/ # Serviços reutilizáveis
│ └── stack_service.go # Serviço de gerenciamento de stack
├── repositories/ # Camada de acesso a dados (futuro)
└── main.go # Entry point (mínimo)
Design Patterns Aplicados¶
1. Dependency Injection Pattern¶
Arquivo: commands/context.go
O AppContext
encapsula todas as dependências compartilhadas entre comandos:
type AppContext struct {
Version string
Commit string
Date string
AgentRegistry interface{}
SurveyAsker taskrunner.SurveyAsker
OutputWriter io.Writer
TestMode bool
// ...
}
Benefícios: - ✅ Facilita testes (mock de dependências) - ✅ Reduz acoplamento - ✅ Torna dependências explícitas - ✅ Permite configuração centralizada
2. Factory Pattern¶
Arquivo: commands/*.go
Cada comando é criado através de uma factory function:
Benefícios: - ✅ Encapsula lógica de criação - ✅ Permite configuração consistente - ✅ Facilita testes unitários - ✅ Suporta diferentes configurações
3. Handler Pattern (Command Handler)¶
Arquivo: handlers/run_handler.go
Separa a lógica de negócio do framework Cobra:
type RunHandler struct {
stackService *services.StackService
config *RunConfig
}
func (h *RunHandler) Execute() error {
// Lógica de negócio aqui
}
Benefícios: - ✅ Lógica de negócio independente do framework - ✅ Facilita testes unitários - ✅ Código reutilizável - ✅ Single Responsibility Principle
4. Service Layer Pattern¶
Arquivo: services/stack_service.go
Encapsula operações de negócio relacionadas:
type StackService struct {
manager *stack.StackManager
}
func (s *StackService) GetOrCreateStack(...) (string, error) {
// Lógica de serviço
}
Benefícios: - ✅ Reutilização de lógica - ✅ Transações e coordenação - ✅ Abstração da camada de dados - ✅ Testabilidade
5. Strategy Pattern (Futuro)¶
Planejado para: Executores (Local, SSH, Agent)
type Executor interface {
Execute(task Task) error
}
type LocalExecutor struct {}
type SSHExecutor struct {}
type AgentExecutor struct {}
Benefícios: - ✅ Diferentes estratégias de execução - ✅ Fácil adicionar novos executores - ✅ Open/Closed Principle
6. Repository Pattern (Futuro)¶
Planejado para: Acesso a dados (DB, API, etc)
type StackRepository interface {
Get(id string) (*Stack, error)
Create(stack *Stack) error
Update(stack *Stack) error
Delete(id string) error
}
Benefícios: - ✅ Abstração de persistência - ✅ Facilita testes com mocks - ✅ Troca de backend transparente
Fluxo de Execução¶
Antes (Monolítico)¶
Depois (Modular)¶
main.go (mínimo)
└─> NewRootCommand(ctx)
└─> NewRunCommand(ctx)
└─> RunHandler.Execute()
├─> StackService.GetOrCreateStack()
├─> RunHandler.initializeSSH()
├─> RunHandler.parseLuaScript()
├─> RunHandler.executeTasks()
└─> StackService.RecordExecution()
Princípios SOLID Aplicados¶
Single Responsibility Principle (SRP)¶
- ✅ Cada classe/struct tem uma única responsabilidade
- ✅
RunHandler
gerencia execução de tasks - ✅
StackService
gerencia stacks - ✅
RunCommand
gerencia apenas CLI
Open/Closed Principle (OCP)¶
- ✅ Aberto para extensão via interfaces
- ✅ Fechado para modificação (comportamento base)
- ✅ Novos executores podem ser adicionados sem alterar código existente
Liskov Substitution Principle (LSP)¶
- ✅ Interfaces podem ser substituídas por implementações
- ✅ Mocks podem substituir serviços reais
Interface Segregation Principle (ISP)¶
- ✅ Interfaces pequenas e específicas
- ✅ Clientes não dependem de métodos que não usam
Dependency Inversion Principle (DIP)¶
- ✅ Dependência de abstrações, não de implementações
- ✅ AppContext injeta dependências
- ✅ Handlers dependem de interfaces de serviços
Benefícios da Refatoração¶
1. Testabilidade¶
- ✅ Handlers podem ser testados sem Cobra
- ✅ Serviços podem ser mockados
- ✅ Lógica de negócio isolada
2. Manutenibilidade¶
- ✅ Código organizado por responsabilidade
- ✅ Arquivos menores e focados
- ✅ Fácil localizar e modificar código
3. Extensibilidade¶
- ✅ Novos comandos seguem mesmo padrão
- ✅ Novos serviços são fáceis de adicionar
- ✅ Novos executores via Strategy Pattern
4. Legibilidade¶
- ✅ Estrutura clara e previsível
- ✅ Nomes descritivos
- ✅ Separação de concerns
Exemplos de Uso¶
Criando um Novo Comando¶
// 1. Criar comando em commands/
func NewMyCommand(ctx *AppContext) *cobra.Command {
return &cobra.Command{
Use: "my-command",
RunE: func(cmd *cobra.Command, args []string) error {
// Extrair flags
// Criar serviços necessários
// Criar handler
// Executar handler
return handler.Execute()
},
}
}
// 2. Criar handler em handlers/
type MyHandler struct {
service *services.MyService
config *MyConfig
}
func (h *MyHandler) Execute() error {
// Lógica de negócio
}
// 3. Criar serviço se necessário em services/
type MyService struct {
// dependências
}
Testando um Handler¶
func TestRunHandler_Execute(t *testing.T) {
// Arrange
mockStackService := &MockStackService{}
config := &handlers.RunConfig{
StackName: "test",
FilePath: "test.sloth",
// ...
}
handler := handlers.NewRunHandler(mockStackService, config)
// Act
err := handler.Execute()
// Assert
assert.NoError(t, err)
assert.True(t, mockStackService.CreateStackCalled)
}
Próximos Passos¶
Curto Prazo¶
- ✅ Extrair comando
run
- ⏳ Extrair comandos
agent/*
- ⏳ Extrair comandos
stack/*
- ⏳ Extrair comandos
scheduler/*
Médio Prazo¶
- ⏳ Implementar Strategy Pattern para executores
- ⏳ Implementar Repository Pattern
- ⏳ Adicionar testes unitários
- ⏳ Adicionar testes de integração
Longo Prazo¶
- ⏳ Métricas e observabilidade
- ⏳ Plugin system
- ⏳ API REST/GraphQL
- ⏳ Web UI
Referências¶
- Clean Architecture - Robert C. Martin
- Domain-Driven Design - Eric Evans
- Enterprise Integration Patterns - Gregor Hohpe
- Refactoring - Martin Fowler
- Design Patterns - Gang of Four
Conclusão¶
A arquitetura modular aplicada ao Sloth Runner segue best practices da indústria e torna o código mais profissional, mantível e extensível. Cada padrão foi escolhido para resolver problemas específicos e melhorar a qualidade geral do código.