Interfaces em Go: introdução prática com exemplos
Desvende o funcionamento das interfaces em Go com explicações diretas, metáforas simples e exemplos práticos de código.
Por que isso é importante
Interfaces são fundamentais para escrita de código desacoplado, testável e reutilizável em linguagens como Go. Entender seu papel como contratos de comportamento — sem se prender à implementação — é uma habilidade essencial para qualquer desenvolvedor backend.
O que é uma interface em Go?
Em Go, uma interface define um conjunto de métodos que um tipo precisa implementar para ser considerado compatível com ela. Diferente de linguagens como Java, essa implementação é implícita: se um tipo possui os métodos pedidos, ele é automaticamente aceito.
// 🎯 Definindo uma interface simples
type Animal interface {
FazerBarulho() string
}
// 🐕 Struct Cachorro
type Cachorro struct {
Nome string
}
// 🐱 Struct Gato
type Gato struct {
Nome string
}
// 🔊 Implementação implícita para Cachorro
func (c Cachorro) FazerBarulho() string {
return "Au au!"
}
// 🔊 Implementação implícita para Gato
func (g Gato) FazerBarulho() string {
return "Miau!"
}
func main() {
// ✅ Ambos tipos satisfazem a interface Animal
var animal1 Animal = Cachorro{Nome: "Rex"}
var animal2 Animal = Gato{Nome: "Mimi"}
fmt.Println(animal1.FazerBarulho()) // Au au!
fmt.Println(animal2.FazerBarulho()) // Miau!
}
ℹ️Atenção
Interfaces não conhecem implementações. Elas apenas exigem a presença de métodos específicos — um verdadeiro contrato de comportamento.
Metáfora simples: o contrato do jogador
Pense em um jogador de futebol assinando com um clube. O contrato não exige saber a posição do atleta, mas apenas que ele jogue conforme acordado. Interfaces seguem esse mesmo conceito: o que importa é o que será feito, não como ou por quem.
// ⚽ Interface representa o "contrato" do jogador
type Jogador interface {
Jogar() string
Treinar() string
}
// 🥅 Goleiro implementa o contrato à sua maneira
type Goleiro struct {
Nome string
}
func (g Goleiro) Jogar() string {
return "Defendendo o gol com as mãos"
}
func (g Goleiro) Treinar() string {
return "Praticando defesas e reflexos"
}
// ⚽ Atacante implementa o contrato diferente
type Atacante struct {
Nome string
}
func (a Atacante) Jogar() string {
return "Marcando gols com os pés"
}
func (a Atacante) Treinar() string {
return "Praticando finalizações"
}
// 🏟️ Função que aceita qualquer jogador (independente da posição)
func EscalarJogador(j Jogador) {
fmt.Printf("Escalando: %s
", j.Jogar())
fmt.Printf("Treinamento: %s
", j.Treinar())
}
func main() {
goleiro := Goleiro{Nome: "Buffon"}
atacante := Atacante{Nome: "Ronaldo"}
// ✅ Ambos cumprem o "contrato" Jogador
EscalarJogador(goleiro)
EscalarJogador(atacante)
}
⚠️Atenção
Ao usar interfaces, você foca nos comportamentos esperados e não na implementação concreta. Isso reduz o acoplamento e permite mais flexibilidade no código.
Definindo comportamentos com interfaces
Suponha uma interface chamada Animal com um método FazerBarulho. Cachorros implementam latido, gatos fazem miau — cada um à sua maneira. O programa apenas exige que eles façam barulho, não importa como.
package main
import "fmt"
// 🔊 Interface define COMPORTAMENTO, não implementação
type Animal interface {
FazerBarulho() string
Mover() string
}
// 🐕 Cachorro tem sua própria implementação
type Cachorro struct {
Raca string
}
func (c Cachorro) FazerBarulho() string {
return "Au au au!"
}
func (c Cachorro) Mover() string {
return "Correndo com 4 patas"
}
// 🐱 Gato implementa os mesmos métodos, mas diferente
type Gato struct {
Cor string
}
func (g Gato) FazerBarulho() string {
return "Miau miau"
}
func (g Gato) Mover() string {
return "Saltando silenciosamente"
}
// 🦅 Pássaro também implementa a interface
type Passaro struct {
Especie string
}
func (p Passaro) FazerBarulho() string {
return "Piu piu"
}
func (p Passaro) Mover() string {
return "Voando com as asas"
}
// 📢 Função que trabalha com qualquer Animal
func FazerAnimalSeComportar(a Animal) {
fmt.Printf("Som: %s
", a.FazerBarulho())
fmt.Printf("Movimento: %s
", a.Mover())
fmt.Println("---")
}
func main() {
// 🎯 Todos são tratados como Animal, cada um se comporta diferente
animais := []Animal{
Cachorro{Raca: "Labrador"},
Gato{Cor: "Preto"},
Passaro{Especie: "Canário"},
}
for _, animal := range animais {
FazerAnimalSeComportar(animal)
}
}
Interface implícita em Go
Em Go, basta um tipo implementar os métodos da interface para que ele seja compatível. Isso simplifica o código e aumenta a flexibilidade; sempre que um tipo definir todos os métodos esperados, ele passa a satisfazer a interface.
package main
import "fmt"
// 📝 Interface Writer simples
type Writer interface {
Write(data string) error
}
// 📄 FileWriter - implementa Writer implicitamente
type FileWriter struct {
filename string
}
func (f FileWriter) Write(data string) error {
fmt.Printf("Escrevendo '%s' no arquivo %s
", data, f.filename)
return nil
}
// 📧 EmailSender - TAMBÉM implementa Writer (sem declarar explicitamente)
type EmailSender struct {
recipient string
}
func (e EmailSender) Write(data string) error {
fmt.Printf("Enviando email para %s: %s
", e.recipient, data)
return nil
}
// 🖨️ Printer - TAMBÉM é um Writer automaticamente
type Printer struct {
brand string
}
func (p Printer) Write(data string) error {
fmt.Printf("Imprimindo via %s: %s
", p.brand, data)
return nil
}
// ✍️ Função genérica que aceita qualquer Writer
func ProcessarDados(w Writer, dados string) {
err := w.Write(dados)
if err != nil {
fmt.Printf("Erro: %v
", err)
}
}
func main() {
// 🎯 Todos são Writers automaticamente (implementação implícita)
file := FileWriter{filename: "dados.txt"}
email := EmailSender{recipient: "user@example.com"}
printer := Printer{brand: "HP"}
dados := "Dados importantes para processamento"
// ✅ Mesma função funciona com tipos diferentes
ProcessarDados(file, dados)
ProcessarDados(email, dados)
ProcessarDados(printer, dados)
// 📋 Slice de interfaces diferentes tipos
writers := []Writer{file, email, printer}
for i, writer := range writers {
ProcessarDados(writer, fmt.Sprintf("Dados %d", i+1))
}
}
Polimorfismo em ação
Interfaces permitem tratar objetos diferentes de forma uniforme. Com uma interface Forma que define o método Área, podemos utilizar tanto Círculo quanto Quadrado sem precisar se preocupar com as diferenças internas de cálculo.
package main
import (
"fmt"
"math"
)
// 📐 Interface Forma define contrato para figuras geométricas
type Forma interface {
Area() float64
Perimetro() float64
Tipo() string
}
// ⭕ Círculo implementa Forma
type Circulo struct {
Raio float64
}
func (c Circulo) Area() float64 {
return math.Pi * c.Raio * c.Raio
}
func (c Circulo) Perimetro() float64 {
return 2 * math.Pi * c.Raio
}
func (c Circulo) Tipo() string {
return "Círculo"
}
// ⬜ Quadrado implementa Forma
type Quadrado struct {
Lado float64
}
func (q Quadrado) Area() float64 {
return q.Lado * q.Lado
}
func (q Quadrado) Perimetro() float64 {
return 4 * q.Lado
}
func (q Quadrado) Tipo() string {
return "Quadrado"
}
// 📊 Triângulo também implementa Forma
type Triangulo struct {
Base, Altura, LadoA, LadoB, LadoC float64
}
func (t Triangulo) Area() float64 {
return (t.Base * t.Altura) / 2
}
func (t Triangulo) Perimetro() float64 {
return t.LadoA + t.LadoB + t.LadoC
}
func (t Triangulo) Tipo() string {
return "Triângulo"
}
// 🧮 Função polimórfica: funciona com qualquer Forma
func CalcularPropriedades(f Forma) {
fmt.Printf("=== %s ===
", f.Tipo())
fmt.Printf("Área: %.2f
", f.Area())
fmt.Printf("Perímetro: %.2f
", f.Perimetro())
fmt.Println()
}
// 📊 Relatório de múltiplas formas
func GerarRelatorio(formas []Forma) {
var areaTotal float64
fmt.Println("📋 RELATÓRIO DE FORMAS GEOMÉTRICAS")
fmt.Println("================================")
for i, forma := range formas {
fmt.Printf("Forma %d:
", i+1)
CalcularPropriedades(forma)
areaTotal += forma.Area()
}
fmt.Printf("🎯 Área total de todas as formas: %.2f
", areaTotal)
}
func main() {
// 🎯 Polimorfismo: diferentes tipos, mesma interface
formas := []Forma{
Circulo{Raio: 5},
Quadrado{Lado: 4},
Triangulo{Base: 6, Altura: 8, LadoA: 6, LadoB: 8, LadoC: 10},
}
// ✅ Mesma função funciona para todos os tipos
GerarRelatorio(formas)
// 🔄 Tratamento uniforme de objetos diferentes
fmt.Println("
🔄 Processamento individual:")
for _, forma := range formas {
CalcularPropriedades(forma)
}
}
✅Info Extra
Isso é polimorfismo: múltiplos tipos com comportamentos distintos, tratados de maneira uniforme.
Desacoplamento com interfaces
Interfaces estabelecem fronteiras claras entre partes do sistema. Arquiteturas como Ports and Adapters se baseiam nesse princípio, permitindo a troca de implementações com mínimo impacto, desde que o contrato continue sendo respeitado.
package main
import "fmt"
// 💾 Interface para persistência (Port)
type UserRepository interface {
Save(user User) error
FindByID(id string) (*User, error)
Delete(id string) error
}
// 📧 Interface para notificações (Port)
type NotificationService interface {
SendWelcomeEmail(userEmail string) error
SendSMS(phone, message string) error
}
// 👤 Domain Model
type User struct {
ID string
Name string
Email string
Phone string
}
// 🏛️ Service/UseCase (não conhece implementações específicas)
type UserService struct {
repo UserRepository // Interface, não implementação concreta
notification NotificationService // Interface, não implementação concreta
}
func NewUserService(repo UserRepository, notif NotificationService) *UserService {
return &UserService{
repo: repo,
notification: notif,
}
}
func (s *UserService) CreateUser(name, email, phone string) error {
user := User{
ID: fmt.Sprintf("user_%d", len(name)), // ID simples para exemplo
Name: name,
Email: email,
Phone: phone,
}
// 💾 Salva usando interface (não sabe se é MySQL, Postgres, etc.)
if err := s.repo.Save(user); err != nil {
return fmt.Errorf("erro ao salvar usuário: %w", err)
}
// 📧 Notifica usando interface (não sabe se é SMTP, SES, etc.)
if err := s.notification.SendWelcomeEmail(user.Email); err != nil {
return fmt.Errorf("erro ao enviar email: %w", err)
}
return nil
}
// 📊 Adapter 1: Banco em memória
type InMemoryUserRepo struct {
users map[string]User
}
func NewInMemoryUserRepo() *InMemoryUserRepo {
return &InMemoryUserRepo{users: make(map[string]User)}
}
func (r *InMemoryUserRepo) Save(user User) error {
r.users[user.ID] = user
fmt.Printf("💾 [InMemory] Usuário %s salvo
", user.Name)
return nil
}
func (r *InMemoryUserRepo) FindByID(id string) (*User, error) {
user, exists := r.users[id]
if !exists {
return nil, fmt.Errorf("usuário não encontrado")
}
return &user, nil
}
func (r *InMemoryUserRepo) Delete(id string) error {
delete(r.users, id)
return nil
}
// 🗄️ Adapter 2: "Banco MySQL" (simulado)
type MySQLUserRepo struct{}
func (r *MySQLUserRepo) Save(user User) error {
fmt.Printf("🗄️ [MySQL] INSERT INTO users VALUES('%s', '%s', '%s')
",
user.ID, user.Name, user.Email)
return nil
}
func (r *MySQLUserRepo) FindByID(id string) (*User, error) {
fmt.Printf("🔍 [MySQL] SELECT * FROM users WHERE id='%s'
", id)
return &User{ID: id, Name: "User from MySQL"}, nil
}
func (r *MySQLUserRepo) Delete(id string) error {
fmt.Printf("🗑️ [MySQL] DELETE FROM users WHERE id='%s'
", id)
return nil
}
// 📨 Adapter 3: Email SMTP (simulado)
type SMTPNotification struct{}
func (n *SMTPNotification) SendWelcomeEmail(email string) error {
fmt.Printf("📨 [SMTP] Enviando email de boas-vindas para %s
", email)
return nil
}
func (n *SMTPNotification) SendSMS(phone, message string) error {
fmt.Printf("📱 [SMTP] SMS para %s: %s
", phone, message)
return nil
}
// 🚀 Adapter 4: AWS SES (simulado)
type AWSNotification struct{}
func (n *AWSNotification) SendWelcomeEmail(email string) error {
fmt.Printf("☁️ [AWS SES] Welcome email sent to %s
", email)
return nil
}
func (n *AWSNotification) SendSMS(phone, message string) error {
fmt.Printf("☁️ [AWS SNS] SMS to %s: %s
", phone, message)
return nil
}
func main() {
fmt.Println("🏗️ DEMONSTRAÇÃO DE DESACOPLAMENTO COM INTERFACES")
fmt.Println("=================================================")
// 🔄 Cenário 1: InMemory + SMTP
fmt.Println("
📂 Cenário 1: InMemory Repository + SMTP Notifications")
inMemoryRepo := NewInMemoryUserRepo()
smtpNotif := &SMTPNotification{}
service1 := NewUserService(inMemoryRepo, smtpNotif)
service1.CreateUser("João", "joao@email.com", "+5511999999999")
// 🔄 Cenário 2: MySQL + AWS (mesma interface!)
fmt.Println("
🗄️ Cenário 2: MySQL Repository + AWS Notifications")
mysqlRepo := &MySQLUserRepo{}
awsNotif := &AWSNotification{}
service2 := NewUserService(mysqlRepo, awsNotif)
service2.CreateUser("Maria", "maria@email.com", "+5511888888888")
// 🔄 Cenário 3: Mix and Match
fmt.Println("
🔀 Cenário 3: InMemory + AWS (mix and match)")
service3 := NewUserService(inMemoryRepo, awsNotif)
service3.CreateUser("Pedro", "pedro@email.com", "+5511777777777")
fmt.Println("
✅ Mesmo código de negócio funciona com diferentes implementações!")
fmt.Println("🎯 Isso é DESACOPLAMENTO em ação!")
}
Métodos implementados: exemplo prático
Expandindo Interfaces com múltiplos métodos
Podemos evoluir a interface Forma e incluir também o método Perímetro. Se um tipo não implementar todos os métodos, ele será incompatível.
❌Erro comum
Remover um dos métodos do tipo — como Perímetro — gera erro de compilação, pois o contrato não é mais respeitado.
Funções genéricas usando interfaces
Ao receber uma interface como parâmetro, uma função passa a aceitar qualquer tipo compatível. Isso reduz duplicação e centraliza comportamentos.
Testes com interfaces simuladas
Interfaces também facilitam testes unitários. Você pode criar uma implementação falsa (mock) do contrato para simular comportamentos sem depender de funções reais.
package main
import (
"errors"
"fmt"
"testing"
)
// 💳 Interface para processamento de pagamentos
type PaymentProcessor interface {
ProcessPayment(amount float64, cardToken string) (*PaymentResult, error)
ValidateCard(cardToken string) bool
}
type PaymentResult struct {
TransactionID string
Status string
Amount float64
}
// 🛒 Service que usa a interface (código de produção)
type OrderService struct {
paymentProcessor PaymentProcessor
}
func NewOrderService(processor PaymentProcessor) *OrderService {
return &OrderService{paymentProcessor: processor}
}
func (o *OrderService) ProcessOrder(amount float64, cardToken string) (*OrderResult, error) {
// 1️⃣ Validar cartão
if !o.paymentProcessor.ValidateCard(cardToken) {
return nil, errors.New("cartão inválido")
}
// 2️⃣ Processar pagamento
payment, err := o.paymentProcessor.ProcessPayment(amount, cardToken)
if err != nil {
return nil, fmt.Errorf("erro no pagamento: %w", err)
}
// 3️⃣ Criar pedido
return &OrderResult{
OrderID: "order_123",
TransactionID: payment.TransactionID,
Total: payment.Amount,
Status: "confirmed",
}, nil
}
type OrderResult struct {
OrderID string
TransactionID string
Total float64
Status string
}
// 🎭 MOCK para testes (implementação falsa)
type MockPaymentProcessor struct {
shouldValidateCard bool
shouldFailPayment bool
callLog []string
}
func (m *MockPaymentProcessor) ValidateCard(cardToken string) bool {
m.callLog = append(m.callLog, fmt.Sprintf("ValidateCard(%s)", cardToken))
return m.shouldValidateCard
}
func (m *MockPaymentProcessor) ProcessPayment(amount float64, cardToken string) (*PaymentResult, error) {
m.callLog = append(m.callLog, fmt.Sprintf("ProcessPayment(%.2f, %s)", amount, cardToken))
if m.shouldFailPayment {
return nil, errors.New("falha na transação")
}
return &PaymentResult{
TransactionID: "txn_mock_123",
Status: "approved",
Amount: amount,
}, nil
}
// 🏭 Implementação real (simulada para exemplo)
type StripePaymentProcessor struct{}
func (s *StripePaymentProcessor) ValidateCard(cardToken string) bool {
fmt.Printf("🌐 [Stripe] Validando cartão %s...
", cardToken)
// Aqui faria requisição real para Stripe
return cardToken != "invalid_card"
}
func (s *StripePaymentProcessor) ProcessPayment(amount float64, cardToken string) (*PaymentResult, error) {
fmt.Printf("💳 [Stripe] Processando pagamento de $%.2f
", amount)
// Aqui faria requisição real para Stripe
return &PaymentResult{
TransactionID: "stripe_txn_456",
Status: "approved",
Amount: amount,
}, nil
}
// 🧪 TESTES
func TestOrderService_ProcessOrder_Success(t *testing.T) {
// Arrange - Configurar mock para sucesso
mockProcessor := &MockPaymentProcessor{
shouldValidateCard: true,
shouldFailPayment: false,
}
orderService := NewOrderService(mockProcessor)
// Act - Executar o teste
result, err := orderService.ProcessOrder(100.0, "valid_card_123")
// Assert - Verificar resultados
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
if result == nil {
t.Fatal("Expected result, got nil")
}
if result.Status != "confirmed" {
t.Errorf("Expected status 'confirmed', got '%s'", result.Status)
}
if result.Total != 100.0 {
t.Errorf("Expected total 100.0, got %.2f", result.Total)
}
// Verificar se métodos foram chamados corretamente
expectedCalls := []string{
"ValidateCard(valid_card_123)",
"ProcessPayment(100.00, valid_card_123)",
}
for i, expectedCall := range expectedCalls {
if i >= len(mockProcessor.callLog) || mockProcessor.callLog[i] != expectedCall {
t.Errorf("Expected call %d to be '%s', got '%s'", i, expectedCall, mockProcessor.callLog[i])
}
}
}
func TestOrderService_ProcessOrder_InvalidCard(t *testing.T) {
// Arrange - Mock configurado para cartão inválido
mockProcessor := &MockPaymentProcessor{
shouldValidateCard: false, // ❌ Cartão inválido
}
orderService := NewOrderService(mockProcessor)
// Act
result, err := orderService.ProcessOrder(100.0, "invalid_card")
// Assert
if err == nil {
t.Error("Expected error for invalid card")
}
if result != nil {
t.Error("Expected nil result for invalid card")
}
if err.Error() != "cartão inválido" {
t.Errorf("Expected 'cartão inválido', got '%s'", err.Error())
}
}
func TestOrderService_ProcessOrder_PaymentFailure(t *testing.T) {
// Arrange - Mock configurado para falha no pagamento
mockProcessor := &MockPaymentProcessor{
shouldValidateCard: true, // ✅ Cartão válido
shouldFailPayment: true, // ❌ Mas pagamento falha
}
orderService := NewOrderService(mockProcessor)
// Act
result, err := orderService.ProcessOrder(100.0, "valid_card")
// Assert
if err == nil {
t.Error("Expected error for payment failure")
}
if result != nil {
t.Error("Expected nil result for payment failure")
}
}
func main() {
fmt.Println("🧪 DEMONSTRAÇÃO DE TESTES COM INTERFACES")
fmt.Println("=======================================")
// 🎯 Demonstração com mock (para testes)
fmt.Println("
🎭 Teste com Mock:")
mockProcessor := &MockPaymentProcessor{
shouldValidateCard: true,
shouldFailPayment: false,
}
orderService := NewOrderService(mockProcessor)
result, err := orderService.ProcessOrder(99.99, "test_card_456")
if err != nil {
fmt.Printf("❌ Erro: %v
", err)
} else {
fmt.Printf("✅ Pedido criado: %+v
", result)
}
fmt.Printf("📋 Calls no mock: %v
", mockProcessor.callLog)
// 🌐 Demonstração com implementação real (simulada)
fmt.Println("
🌐 Produção com Stripe:")
stripeProcessor := &StripePaymentProcessor{}
orderService2 := NewOrderService(stripeProcessor)
result2, err2 := orderService2.ProcessOrder(99.99, "stripe_card_789")
if err2 != nil {
fmt.Printf("❌ Erro: %v
", err2)
} else {
fmt.Printf("✅ Pedido criado: %+v
", result2)
}
fmt.Println("
🎯 Mesma lógica de negócio, diferentes implementações!")
fmt.Println("🧪 Mocks permitem testes rápidos e confiáveis!")
}
ℹ️Dica de Teste
Crie mocks que retornam valores previsíveis e use-os para validar a lógica de funções que dependem da interface.
Interfaces e Clean Architecture
Em arquiteturas limpas, interfaces separam comportamentos das dependências externas. Isso aumenta a testabilidade do domínio e a flexibilidade nas trocas de componentes.
package main
import (
"fmt"
"time"
)
// ========================================
// 🏛️ DOMAIN LAYER (Entidades e Interfaces)
// ========================================
// 📋 Entidade de domínio
type Product struct {
ID string
Name string
Price float64
Stock int
CreatedAt time.Time
}
// 📝 Value Object
type OrderID struct {
Value string
}
// 🔥 Agregado raiz
type Order struct {
ID OrderID
Products []Product
Total float64
Status string
}
// 🚪 Interfaces de domínio (Ports) - definidas no domínio
type ProductRepository interface {
FindByID(id string) (*Product, error)
Save(product *Product) error
UpdateStock(id string, newStock int) error
}
type OrderRepository interface {
Save(order *Order) error
FindByID(orderID OrderID) (*Order, error)
}
type EmailService interface {
SendOrderConfirmation(order *Order) error
}
type InventoryService interface {
ReserveStock(productID string, quantity int) error
ReleaseStock(productID string, quantity int) error
}
// ========================================
// 🏭 USE CASES (Application Layer)
// ========================================
// 🛒 Use Case para processar pedido
type ProcessOrderUseCase struct {
productRepo ProductRepository // Depende da interface, não implementação
orderRepo OrderRepository // Depende da interface, não implementação
emailService EmailService // Depende da interface, não implementação
inventory InventoryService // Depende da interface, não implementação
}
func NewProcessOrderUseCase(
productRepo ProductRepository,
orderRepo OrderRepository,
emailService EmailService,
inventory InventoryService,
) *ProcessOrderUseCase {
return &ProcessOrderUseCase{
productRepo: productRepo,
orderRepo: orderRepo,
emailService: emailService,
inventory: inventory,
}
}
func (uc *ProcessOrderUseCase) Execute(productID string, quantity int) (*Order, error) {
// 1️⃣ Buscar produto (via interface)
product, err := uc.productRepo.FindByID(productID)
if err != nil {
return nil, fmt.Errorf("produto não encontrado: %w", err)
}
// 2️⃣ Verificar estoque
if product.Stock < quantity {
return nil, fmt.Errorf("estoque insuficiente")
}
// 3️⃣ Reservar estoque (via interface)
if err := uc.inventory.ReserveStock(productID, quantity); err != nil {
return nil, fmt.Errorf("erro ao reservar estoque: %w", err)
}
// 4️⃣ Criar pedido (lógica de domínio)
order := &Order{
ID: OrderID{Value: fmt.Sprintf("order_%d", time.Now().Unix())},
Products: []Product{*product},
Total: product.Price * float64(quantity),
Status: "confirmed",
}
// 5️⃣ Salvar pedido (via interface)
if err := uc.orderRepo.Save(order); err != nil {
// 🔄 Rollback do estoque em caso de erro
uc.inventory.ReleaseStock(productID, quantity)
return nil, fmt.Errorf("erro ao salvar pedido: %w", err)
}
// 6️⃣ Enviar confirmação (via interface)
if err := uc.emailService.SendOrderConfirmation(order); err != nil {
// ⚠️ Log do erro, mas não falha o pedido
fmt.Printf("Aviso: erro ao enviar email de confirmação: %v
", err)
}
return order, nil
}
// ========================================
// 🔌 INFRASTRUCTURE LAYER (Adapters)
// ========================================
// 💾 Adapter PostgreSQL (implementa ProductRepository)
type PostgreSQLProductRepo struct {
connectionString string
}
func (r *PostgreSQLProductRepo) FindByID(id string) (*Product, error) {
fmt.Printf("🗄️ [PostgreSQL] SELECT * FROM products WHERE id = '%s'
", id)
// Simulação de busca no banco
return &Product{
ID: id,
Name: "Produto " + id,
Price: 99.99,
Stock: 10,
}, nil
}
func (r *PostgreSQLProductRepo) Save(product *Product) error {
fmt.Printf("💾 [PostgreSQL] Salvando produto: %s
", product.Name)
return nil
}
func (r *PostgreSQLProductRepo) UpdateStock(id string, newStock int) error {
fmt.Printf("📦 [PostgreSQL] UPDATE products SET stock = %d WHERE id = '%s'
", newStock, id)
return nil
}
// 📋 Adapter MongoDB (implementa OrderRepository)
type MongoOrderRepo struct {
database string
}
func (r *MongoOrderRepo) Save(order *Order) error {
fmt.Printf("🍃 [MongoDB] Salvando pedido %s no banco %s
", order.ID.Value, r.database)
return nil
}
func (r *MongoOrderRepo) FindByID(orderID OrderID) (*Order, error) {
fmt.Printf("🔍 [MongoDB] Buscando pedido %s
", orderID.Value)
return nil, nil
}
// 📧 Adapter SendGrid (implementa EmailService)
type SendGridEmailService struct {
apiKey string
}
func (s *SendGridEmailService) SendOrderConfirmation(order *Order) error {
fmt.Printf("📧 [SendGrid] Enviando confirmação do pedido %s (Total: $%.2f)
",
order.ID.Value, order.Total)
return nil
}
// 📦 Adapter Redis (implementa InventoryService)
type RedisInventoryService struct {
redisAddr string
}
func (r *RedisInventoryService) ReserveStock(productID string, quantity int) error {
fmt.Printf("🔒 [Redis] Reservando %d unidades do produto %s
", quantity, productID)
return nil
}
func (r *RedisInventoryService) ReleaseStock(productID string, quantity int) error {
fmt.Printf("🔓 [Redis] Liberando %d unidades do produto %s
", quantity, productID)
return nil
}
// ========================================
// 🏭 DEPENDENCY INJECTION / MAIN
// ========================================
func main() {
fmt.Println("🏛️ CLEAN ARCHITECTURE COM INTERFACES")
fmt.Println("===================================")
// 🔧 Criar adaptadores (camada de infraestrutura)
productRepo := &PostgreSQLProductRepo{connectionString: "postgres://..."}
orderRepo := &MongoOrderRepo{database: "orders_db"}
emailService := &SendGridEmailService{apiKey: "sg_api_key"}
inventoryService := &RedisInventoryService{redisAddr: "redis://localhost:6379"}
// 🏭 Injetar dependências no use case (aplicação só conhece interfaces)
processOrder := NewProcessOrderUseCase(
productRepo, // Interface ProductRepository
orderRepo, // Interface OrderRepository
emailService, // Interface EmailService
inventoryService, // Interface InventoryService
)
// 🚀 Executar use case
fmt.Println("
📦 Processando pedido...")
order, err := processOrder.Execute("product_123", 2)
if err != nil {
fmt.Printf("❌ Erro: %v
", err)
return
}
fmt.Printf("✅ Pedido processado com sucesso!
")
fmt.Printf(" ID: %s
", order.ID.Value)
fmt.Printf(" Total: $%.2f
", order.Total)
fmt.Printf(" Status: %s
", order.Status)
fmt.Println("
🎯 BENEFÍCIOS DESTA ARQUITETURA:")
fmt.Println("• ✅ Use Case não depende de tecnologias específicas")
fmt.Println("• 🔄 Fácil troca de implementações (PostgreSQL → MySQL)")
fmt.Println("• 🧪 Testes simples com mocks")
fmt.Println("• 📦 Camadas bem separadas")
fmt.Println("• 🏛️ Regras de negócio isoladas na camada de domínio")
}
Bindings dinâmicos com interface
Go permite alternar contexto de execução usando interfaces. Basta o tipo seguir a assinatura desejada. O binding (ligação) é automático e dinâmico.
package main
import (
"fmt"
"math/rand"
"time"
)
// 🎮 Interface para diferentes estratégias de jogo
type GameStrategy interface {
PlayTurn() string
GetDifficulty() string
}
// 😊 Estratégia Fácil
type EasyStrategy struct {
playerName string
}
func (e EasyStrategy) PlayTurn() string {
moves := []string{"Ataque básico", "Defesa simples", "Movimento cauteloso"}
return moves[rand.Intn(len(moves))]
}
func (e EasyStrategy) GetDifficulty() string {
return "Fácil"
}
// 🔥 Estratégia Difícil
type HardStrategy struct {
playerName string
}
func (h HardStrategy) PlayTurn() string {
moves := []string{"Combo devastador", "Contra-ataque perfeito", "Estratégia complexa"}
return moves[rand.Intn(len(moves))]
}
func (h HardStrategy) GetDifficulty() string {
return "Difícil"
}
// 🤖 Estratégia de IA
type AIStrategy struct {
version string
}
func (ai AIStrategy) PlayTurn() string {
return fmt.Sprintf("IA v%s calculou o movimento ótimo", ai.version)
}
func (ai AIStrategy) GetDifficulty() string {
return "IA"
}
// 🎯 Contexto do jogo (usa interface)
type GameContext struct {
strategy GameStrategy // Binding dinâmico!
round int
}
func (g *GameContext) SetStrategy(strategy GameStrategy) {
g.strategy = strategy // 🔄 Troca estratégia dinamicamente
fmt.Printf("🔄 Estratégia alterada para: %s
", strategy.GetDifficulty())
}
func (g *GameContext) PlayRound() {
g.round++
fmt.Printf("🎮 Round %d [%s]: %s
",
g.round, g.strategy.GetDifficulty(), g.strategy.PlayTurn())
}
// 🏭 Factory para criar estratégias dinamicamente
func CreateStrategy(level string) GameStrategy {
switch level {
case "easy":
return EasyStrategy{playerName: "Iniciante"}
case "hard":
return HardStrategy{playerName: "Veterano"}
case "ai":
return AIStrategy{version: "2.0"}
default:
return EasyStrategy{playerName: "Padrão"}
}
}
// 📊 Sistema de progressão que muda estratégia automaticamente
type ProgressionSystem struct {
game *GameContext
score int
levels []string
}
func (p *ProgressionSystem) UpdateDifficulty() {
var newLevel string
switch {
case p.score < 100:
newLevel = "easy"
case p.score < 500:
newLevel = "hard"
default:
newLevel = "ai"
}
// 🔄 Binding dinâmico baseado na pontuação
newStrategy := CreateStrategy(newLevel)
p.game.SetStrategy(newStrategy)
}
func (p *ProgressionSystem) AddScore(points int) {
p.score += points
fmt.Printf("📈 Pontuação: %d (+%d)
", p.score, points)
p.UpdateDifficulty()
}
// 🔀 Demonstração de polimorfismo dinâmico
func DemonstratePolymorphism() {
strategies := []GameStrategy{
EasyStrategy{playerName: "João"},
HardStrategy{playerName: "Maria"},
AIStrategy{version: "3.0"},
}
fmt.Println("
🔀 POLIMORFISMO DINÂMICO:")
for i, strategy := range strategies {
fmt.Printf("Estratégia %d: %s - %s
",
i+1, strategy.GetDifficulty(), strategy.PlayTurn())
}
}
func main() {
rand.Seed(time.Now().UnixNano())
fmt.Println("🎮 DEMONSTRAÇÃO DE BINDINGS DINÂMICOS")
fmt.Println("===================================")
// 🎯 Criar contexto do jogo
game := &GameContext{}
// 🔄 Binding dinâmico: diferentes estratégias em runtime
fmt.Println("
🔄 Alterando estratégias dinamicamente:")
// Começar com estratégia fácil
game.SetStrategy(CreateStrategy("easy"))
game.PlayRound()
// Mudar para difícil
game.SetStrategy(CreateStrategy("hard"))
game.PlayRound()
// Mudar para IA
game.SetStrategy(CreateStrategy("ai"))
game.PlayRound()
// 📊 Sistema de progressão automática
fmt.Println("
📊 Sistema de progressão automática:")
progression := &ProgressionSystem{
game: game,
score: 0,
levels: []string{"easy", "hard", "ai"},
}
// Simular progressão no jogo
progression.AddScore(50) // Ainda fácil
progression.game.PlayRound()
progression.AddScore(100) // Agora difícil
progression.game.PlayRound()
progression.AddScore(400) // Agora IA
progression.game.PlayRound()
// 🔀 Demonstrar polimorfismo
DemonstratePolymorphism()
fmt.Println("
🎯 CARACTERÍSTICAS DOS BINDINGS DINÂMICOS:")
fmt.Println("• 🔄 Troca de implementação em tempo de execução")
fmt.Println("• 🏭 Factory pattern para criação dinâmica")
fmt.Println("• 📊 Adaptação baseada em estado/contexto")
fmt.Println("• 🎮 Mesmo código funciona com diferentes comportamentos")
fmt.Println("• ⚡ Performance mantida (binding é automático)")
}
Encapsulamento com interface
A implementação de métodos fica escondida do restante do software. O código depende apenas da interface, promovendo encapsulamento natural.
package main
import (
"fmt"
"log"
"time"
)
// 🔐 Interface pública (contrato exposto)
type SecureWallet interface {
GetBalance() float64
Transfer(amount float64, destination string) error
GetTransactionHistory() []string
}
// 🏦 Implementação privada (detalhes encapsulados)
type cryptoWallet struct {
// 🔒 Campos privados (minúsculos) - não acessíveis externamente
privateKey string
balance float64
transactions []transaction
encryptionKey string
securityLevel int
}
// 📝 Estrutura interna privada
type transaction struct {
id string
amount float64
timestamp time.Time
toAddress string
fromAddress string
}
// 🏭 Constructor (função pública que retorna interface)
func NewSecureWallet(initialBalance float64) SecureWallet {
return &cryptoWallet{
privateKey: generatePrivateKey(), // Função interna
balance: initialBalance,
transactions: make([]transaction, 0),
encryptionKey: generateEncryptionKey(), // Função interna
securityLevel: 5,
}
}
// 🔐 Métodos PÚBLICOS da interface (contratos expostos)
func (w *cryptoWallet) GetBalance() float64 {
// 🛡️ Validação de segurança interna (encapsulada)
if !w.validateAccess() {
log.Println("⚠️ Acesso negado: validação de segurança falhou")
return 0
}
return w.balance // Cliente só vê o resultado, não como é calculado
}
func (w *cryptoWallet) Transfer(amount float64, destination string) error {
// 🔍 Validações internas (encapsuladas)
if !w.validateTransfer(amount) {
return fmt.Errorf("transferência inválida")
}
if !w.hasEnoughBalance(amount) {
return fmt.Errorf("saldo insuficiente")
}
// 🔐 Processamento criptográfico (encapsulado)
encryptedTx := w.encryptTransaction(amount, destination)
// 💾 Armazenamento seguro (encapsulado)
w.storeTransaction(encryptedTx)
// 💰 Atualização de saldo (encapsulada)
w.updateBalance(-amount)
return nil
}
func (w *cryptoWallet) GetTransactionHistory() []string {
// 🔍 Cliente recebe dados formatados, não estrutura interna
history := make([]string, len(w.transactions))
for i, tx := range w.transactions {
// 🎭 Formatar dados sem expor estrutura interna
history[i] = w.formatTransaction(tx)
}
return history
}
// 🔒 Métodos PRIVADOS (implementação encapsulada)
func (w *cryptoWallet) validateAccess() bool {
// Lógica complexa de segurança escondida
return w.securityLevel >= 3
}
func (w *cryptoWallet) validateTransfer(amount float64) bool {
return amount > 0 && amount <= 10000 // Limite interno
}
func (w *cryptoWallet) hasEnoughBalance(amount float64) bool {
return w.balance >= amount
}
func (w *cryptoWallet) encryptTransaction(amount float64, destination string) transaction {
// Criptografia complexa escondida do cliente
return transaction{
id: generateTransactionID(),
amount: amount,
timestamp: time.Now(),
toAddress: destination,
fromAddress: w.privateKey[0:8] + "...", // Endereço ofuscado
}
}
func (w *cryptoWallet) storeTransaction(tx transaction) {
w.transactions = append(w.transactions, tx)
}
func (w *cryptoWallet) updateBalance(delta float64) {
w.balance += delta
// Poderia ter log, auditoria, etc. - tudo encapsulado
}
func (w *cryptoWallet) formatTransaction(tx transaction) string {
return fmt.Sprintf("[%s] %.2f para %s em %s",
tx.id, tx.amount, tx.toAddress, tx.timestamp.Format("15:04:05"))
}
// 🛠️ Funções utilitárias privadas (não expostas)
func generatePrivateKey() string {
return "pk_" + fmt.Sprintf("%d", time.Now().UnixNano())
}
func generateEncryptionKey() string {
return "ek_" + fmt.Sprintf("%d", time.Now().UnixNano())
}
func generateTransactionID() string {
return fmt.Sprintf("tx_%d", time.Now().UnixNano())
}
// 🏛️ Serviço que usa a wallet (só conhece a interface)
type BankingService struct {
wallets map[string]SecureWallet // Só interface, não implementação
}
func NewBankingService() *BankingService {
return &BankingService{
wallets: make(map[string]SecureWallet),
}
}
func (b *BankingService) CreateAccount(userID string, initialBalance float64) {
// 🎯 Cria wallet, mas só vê a interface
wallet := NewSecureWallet(initialBalance)
b.wallets[userID] = wallet
fmt.Printf("✅ Conta criada para %s com saldo inicial: %.2f
",
userID, wallet.GetBalance())
}
func (b *BankingService) ProcessTransfer(fromUser, toUser string, amount float64) {
fromWallet := b.wallets[fromUser]
if fromWallet == nil {
fmt.Printf("❌ Usuário %s não encontrado
", fromUser)
return
}
// 🔐 Usa apenas métodos da interface (implementação escondida)
err := fromWallet.Transfer(amount, toUser)
if err != nil {
fmt.Printf("❌ Transferência falhou: %v
", err)
return
}
fmt.Printf("✅ Transferência de %.2f de %s para %s realizada
",
amount, fromUser, toUser)
}
func main() {
fmt.Println("🔐 DEMONSTRAÇÃO DE ENCAPSULAMENTO COM INTERFACES")
fmt.Println("===============================================")
// 🏦 Criar serviço bancário
bank := NewBankingService()
// 👥 Criar contas
bank.CreateAccount("alice", 1000.0)
bank.CreateAccount("bob", 500.0)
fmt.Println("
💰 Estado inicial:")
aliceWallet := bank.wallets["alice"]
bobWallet := bank.wallets["bob"]
fmt.Printf("Alice: %.2f
", aliceWallet.GetBalance())
fmt.Printf("Bob: %.2f
", bobWallet.GetBalance())
// 💸 Realizar transferências
fmt.Println("
💸 Transferências:")
bank.ProcessTransfer("alice", "bob", 200.0)
bank.ProcessTransfer("alice", "bob", 50.0)
// 📊 Estado final
fmt.Println("
📊 Estado final:")
fmt.Printf("Alice: %.2f
", aliceWallet.GetBalance())
fmt.Printf("Bob: %.2f
", bobWallet.GetBalance())
// 📋 Histórico de transações
fmt.Println("
📋 Histórico da Alice:")
history := aliceWallet.GetTransactionHistory()
for _, tx := range history {
fmt.Printf(" %s
", tx)
}
fmt.Println("
🎯 BENEFÍCIOS DO ENCAPSULAMENTO:")
fmt.Println("• 🔒 Implementação criptográfica escondida")
fmt.Println("• 🛡️ Validações de segurança internas")
fmt.Println("• 🏦 Cliente só vê interface limpa")
fmt.Println("• 🔧 Implementação pode mudar sem afetar clientes")
fmt.Println("• 📦 Complexidade interna isolada")
fmt.Println("• 🎭 Dados formatados antes da exposição")
}
Mudanças para crescimento do projeto
Conforme o projeto cresce, o uso de interfaces permite adicionar novas formas, comportamentos e algoritmos sem afetar as dependências existentes.
Resumo das boas práticas
Defina interfaces pequenas, bem nomeadas, com comportamentos específicos. Não exponha métodos desnecessários. Use composição para combiná-las em casos complexos.