refatorando a arquitetura

This commit is contained in:
2026-07-15 18:47:19 -03:00
parent e57de50bc2
commit 9d1d75f3af
12 changed files with 374 additions and 307 deletions
+20
View File
@@ -0,0 +1,20 @@
package main
import (
"controle-de-estoque/internal/cli"
inmemory "controle-de-estoque/internal/repository/in_memory"
"controle-de-estoque/internal/service"
)
func main() {
// Aqui é o único lugar que "conhece" todas as camadas.
// Para trocar a persistência (ex: Postgres, arquivo JSON),
// basta trocar esta linha por outra implementação de
// repository.InventoryRepository — nada mais muda.
repo := inmemory.NewInventorytRepository()
svc := service.NewInventoryService(repo)
app := cli.NewApp(svc)
app.Run()
}
-78
View File
@@ -1,78 +0,0 @@
package controller
import (
"controle-de-estoque/model"
"controle-de-estoque/view"
)
type Controller struct {
inventory *model.Inventory
}
func New(inventory *model.Inventory) *Controller {
return &Controller{inventory: inventory}
}
// Método principal de entrada da CLI
func (c *Controller) Execute() {
for {
view.ShowMenu()
input, err := view.ReadMenuInput()
if err != nil {
view.ShowError(err)
break
}
switch input {
case "1", "adicionar":
c.AddProduct()
case "2", "listar":
c.listAllProducts()
case "3", "atualizar":
c.updateProduct()
case "4", "deletar":
c.deleteProduct()
default:
view.ShowMessage("Sistema encerrado!", nil)
return
}
}
}
func (c *Controller) AddProduct() {
productInput := view.ReadAddProductInput()
p := c.inventory.AddProduct(productInput)
view.ShowMessage("Produto criado com ID %d: %s\n", []any{p.ID, p.Name})
}
func (c *Controller) listAllProducts() {
products := c.inventory.FetchProducts()
view.ShowAllProducts(products)
}
func (c *Controller) updateProduct() {
productID, field := view.ReadEditProductInput()
value := view.ReadNewValueInput(field)
updated, err := c.inventory.UpdateProduct(productID, field, value)
if err != nil {
view.ShowError(err)
return
}
view.ShowMessage("Produto %d atualizado com sucesso!\n", []any{updated.ID})
}
func (c *Controller) deleteProduct() {
productID := view.ReadDeleteProductInput()
if err := c.inventory.DeleteProduct(productID); err != nil {
view.ShowError(err)
return
}
view.ShowMessage("Produto %d deletado com sucesso!", []any{productID})
}
+58
View File
@@ -0,0 +1,58 @@
package cli
import (
"bufio"
"controle-de-estoque/internal/service"
"fmt"
"os"
"strings"
)
// App representa a aplicação de linha de comando.
// Ele faz o papel de "controller" + "router": lê a entrada do usuário,
// decide qual ação executar e chama o service correspondente.
type App struct {
service *service.InventorySevice
reader *bufio.Scanner
}
func NewApp(s *service.InventorySevice) *App {
return &App{
service: s,
reader: bufio.NewScanner(os.Stdin),
}
}
func (a *App) Run() {
for {
ShowMenu()
if !a.reader.Scan() {
return
}
input := strings.TrimSpace(a.reader.Text())
switch strings.ToLower(input) {
case "1", "adicionar":
a.handleAdd()
case "2", "listar":
a.handleList()
case "3", "atualizar":
a.handleUpdate()
case "4", "deletar":
a.handleDelete()
case "0", "sair":
fmt.Println("Saindo...")
return
default:
fmt.Println("Opção inválida, tente novamente.")
}
}
}
// Helper para pedir e ler uma linha de texto do usuário.
func (a *App) readLine(msg string) string {
fmt.Print(msg)
a.reader.Scan()
return strings.TrimSpace(a.reader.Text())
}
+93
View File
@@ -0,0 +1,93 @@
package cli
import (
"fmt"
"strconv"
)
func (a *App) handleAdd() {
name := a.readLine("Nome do produto: ")
category := a.readLine("Categoria do produto: ")
priceStr := a.readLine("Preço: ")
price, err := strconv.ParseFloat(priceStr, 64)
if err != nil {
fmt.Println("Preço inválido.")
return
}
qtyStr := a.readLine("Quantidade: ")
qty, err := strconv.Atoi(qtyStr)
if err != nil {
fmt.Println("Quantidade inválida.")
return
}
product, err := a.service.CreateProduct(name, category, price, qty)
if err != nil {
fmt.Println("Erro ao adicionar:", err)
return
}
fmt.Printf("Produto adicionado: #%d %s\n", product.ID, product.Name)
}
func (a *App) handleList() {
products := a.service.ListProducts()
if len(products) == 0 {
fmt.Println("Nenhum produto cadastrado.")
return
}
for _, p := range products {
fmt.Printf(
"==============================================="+"\n"+
"ID PRODUTO: %d"+"\n"+
"NOME: %s"+"\n"+
"CATEGORIA: %s"+"\n"+
"PREÇO: %.2f"+"\n"+
"QUANTIDADE: %d"+"\n"+
"==============================================="+"\n",
p.ID, p.Name, p.Category, p.Price, p.Quantity)
}
}
func (a *App) handleUpdate() {
idStr := a.readLine("Informe o Id do produto que deseja atualizar: ")
id, err := strconv.Atoi(idStr)
if err != nil {
fmt.Println("ID inválido.")
return
}
field := a.readLine("Informe o campo que deseja atualizar (nome, categoria, preco, quantidade): ")
value := a.readLine(fmt.Sprintf("Informe o novo valor para %s: ", field))
product, err := a.service.UpdateProduct(id, field, value)
if err != nil {
fmt.Println("Erro ao atualizar:", err)
return
}
fmt.Printf("Produto %d atualizado com sucesso!\n", product.ID)
}
func (a *App) handleDelete() {
idStr := a.readLine("Informe o Id do produto que deseja deletar: ")
id, err := strconv.Atoi(idStr)
if err != nil {
fmt.Println("ID inválido.")
return
}
if err := a.service.DeleteProduct(id); err != nil {
fmt.Println("Erro ao deletar:", err)
return
}
fmt.Printf("Produto %d deletado com sucesso!\n", id)
}
+16
View File
@@ -0,0 +1,16 @@
package cli
import "fmt"
func ShowMenu() {
fmt.Print(`
============ CONTROLE DE ESTOQUE ==============
ESCOLHA UMA DAS OPÇÕES:
1 / adicionar - Adiciona o produto no estoque
2 / listar - Lista todos os produtos do estoque
3 / atualizar - Atualiza um produto do estoque
4 / deletar - Deleta um produto do estoque
0 / sair - Sair do programa
=================================================
Opção: `)
}
+16
View File
@@ -0,0 +1,16 @@
package model
type Product struct {
ID int
Name string
Category string
Price float64
Quantity int
}
type ProductInput struct {
Name string
Category string
Price float64
Quantity int
}
@@ -0,0 +1,69 @@
package inmemory
import (
"controle-de-estoque/internal/model"
"errors"
)
var ErrNotFound = errors.New("Produto não encontrado!")
type InventoryRepository struct {
products map[int]*model.Product
nextID int
}
func NewInventorytRepository() *InventoryRepository {
return &InventoryRepository{
products: make(map[int]*model.Product),
nextID: 1,
}
}
func (r *InventoryRepository) Create(p *model.ProductInput) *model.Product {
product := model.Product{
ID: r.nextID,
Name: p.Name,
Category: p.Category,
Price: p.Price,
Quantity: p.Quantity,
}
r.products[r.nextID] = &product
r.nextID++
return &product
}
func (r *InventoryRepository) FindByID(id int) (*model.Product, error) {
product, ok := r.products[id]
if !ok {
return nil, ErrNotFound
}
return product, nil
}
func (r *InventoryRepository) FindAll() map[int]*model.Product {
return r.products
}
func (r *InventoryRepository) Update(p *model.Product) error {
if _, ok := r.products[p.ID]; !ok {
return ErrNotFound
}
r.products[p.ID] = p
return nil
}
func (r *InventoryRepository) Delete(id int) error {
if _, ok := r.products[id]; !ok {
return ErrNotFound
}
delete(r.products, id)
return nil
}
@@ -0,0 +1,11 @@
package repository
import "controle-de-estoque/internal/model"
type InventoryRepository interface {
Create(p *model.ProductInput) *model.Product
FindByID(id int) (*model.Product, error)
FindAll() map[int]*model.Product
Update(p *model.Product) error
Delete(id int) error
}
+91
View File
@@ -0,0 +1,91 @@
package service
import (
"controle-de-estoque/internal/model"
"controle-de-estoque/internal/repository"
"errors"
"strconv"
"strings"
)
type InventorySevice struct {
repo repository.InventoryRepository
}
func NewInventoryService(repo repository.InventoryRepository) *InventorySevice {
return &InventorySevice{repo: repo}
}
func (s *InventorySevice) CreateProduct(
name string,
category string,
price float64,
quantity int) (*model.Product, error) {
if name == "" {
return nil, errors.New("O nome é obrigatório")
}
if quantity < 0 {
return nil, errors.New("A quantidade não pode ser negativa!")
}
productInput := &model.ProductInput{
Name: name,
Category: category,
Price: price,
Quantity: quantity,
}
product := s.repo.Create(productInput)
return product, nil
}
func (s *InventorySevice) ListProducts() map[int]*model.Product {
return s.repo.FindAll()
}
func (s *InventorySevice) GetProduct(id int) (*model.Product, error) {
return s.repo.FindByID(id)
}
func (s *InventorySevice) UpdateProduct(id int, field, value string) (*model.Product, error) {
product, err := s.repo.FindByID(id)
if err != nil {
return nil, err
}
switch strings.ToLower(field) {
case "nome", "name":
product.Name = value
case "categoria", "category":
product.Category = value
case "preco", "preço", "price":
price, err := strconv.ParseFloat(value, 64)
if err != nil {
return nil, errors.New("Preço inválido")
}
product.Price = price
case "quantidade", "quantity":
quantity, err := strconv.Atoi(value)
if err != nil {
return nil, errors.New("Quantidade inválida")
}
product.Quantity = quantity
default:
return nil, errors.New("Campo inválido")
}
err = s.repo.Update(product)
if err != nil {
return nil, err
}
return product, nil
}
func (s *InventorySevice) DeleteProduct(id int) error {
return s.repo.Delete(id)
}
-12
View File
@@ -1,12 +0,0 @@
package main
import (
"controle-de-estoque/controller"
"controle-de-estoque/model"
)
func main() {
estoque := model.NewInventory()
controller := controller.New(estoque)
controller.Execute()
}
-98
View File
@@ -1,98 +0,0 @@
package model
import (
"errors"
"strconv"
"strings"
)
type ProductID int
var productID ProductID = 1
type Product struct {
ID ProductID
Name string
Category string
Price float64
Quantity int
}
type ProductInput struct {
Name string
Category string
Price float64
Quantity int
}
type Inventory struct {
products map[ProductID]Product
}
// função auxiliar para criar a instância do objeto Inventory
func NewInventory() *Inventory {
return &Inventory{products: make(map[ProductID]Product)}
}
func (e *Inventory) AddProduct(i *ProductInput) *Product {
product := Product{
ID: productID,
Name: i.Name,
Category: i.Category,
Price: i.Price,
Quantity: i.Quantity,
}
e.products[productID] = product
productID += 1
return &product
}
func (e *Inventory) FetchProducts() map[ProductID]Product {
return e.products
}
func (e *Inventory) UpdateProduct(id ProductID, field, value string) (*Product, error) {
product, ok := e.products[id]
if !ok {
return nil, errors.New("Produto não encontrado")
}
switch strings.ToLower(field) {
case "nome", "name":
product.Name = value
case "categoria", "category":
product.Category = value
case "preco", "preço", "price":
price, err := strconv.ParseFloat(value, 64)
if err != nil {
return nil, errors.New("Preço inválido")
}
product.Price = price
case "quantidade", "quantity":
quantity, err := strconv.Atoi(value)
if err != nil {
return nil, errors.New("Quantidade inválida")
}
product.Quantity = quantity
default:
return nil, errors.New("Campo inválido")
}
e.products[id] = product
return &product, nil
}
func (e *Inventory) DeleteProduct(id ProductID) error {
if _, ok := e.products[id]; !ok {
return errors.New("Produto não encontrado")
}
delete(e.products, id)
return nil
}
-119
View File
@@ -1,119 +0,0 @@
package view
import (
"bufio"
"controle-de-estoque/model"
"errors"
"fmt"
"os"
"strconv"
"strings"
)
var reader = bufio.NewReader(os.Stdin)
func ShowMenu() {
fmt.Print(`
============ CONTROLE DE ESTOQUE ==============
ESCOLHA UMA DAS OPÇÕES:
1 / adicionar - Adiciona o produto no estoque
2 / listar - Lista todos os produtos do estoque
3 / atualizar - Atualiza um produto do estoque
4 / deletar - Deleta um produto do estoque
0 / sair - Sair do programa
=================================================
`)
}
func ShowError(err error) {
fmt.Println("Error: ", err)
}
func ShowMessage(msg string, args []any) {
if args == nil {
fmt.Println(msg)
return
}
fmt.Printf(msg, args...)
}
func ReadMenuInput() (string, error) {
input, err := reader.ReadString('\n')
input = strings.TrimSpace(input)
if err != nil || input == "" {
return input, errors.New("Opção Inválida!")
}
return input, nil
}
func ReadAddProductInput() *model.ProductInput {
fmt.Print("Nome do produto: ")
name, _ := reader.ReadString('\n')
fmt.Print("Categoria do produto: ")
category, _ := reader.ReadString('\n')
fmt.Print("Preço: ")
precoStr, _ := reader.ReadString('\n')
price, _ := strconv.ParseFloat(strings.TrimSpace(precoStr), 64)
fmt.Print("Quantidade: ")
qtyStr, _ := reader.ReadString('\n')
quantity, _ := strconv.Atoi(strings.TrimSpace(qtyStr))
return &model.ProductInput{
Name: strings.TrimSpace(name),
Category: strings.TrimSpace(category),
Price: price,
Quantity: quantity,
}
}
func ReadEditProductInput() (model.ProductID, string) {
fmt.Print("Informe o Id do produto que deseja atualizar: ")
productID, _ := reader.ReadString('\n')
productIDInt, _ := strconv.Atoi(strings.TrimSpace(productID))
fmt.Print("Informe o nome do campo que deseja atualizar (nome, categoria, preco, quantidade): ")
field, _ := reader.ReadString('\n')
field = strings.TrimSpace(field)
return model.ProductID(productIDInt), field
}
func ReadDeleteProductInput() model.ProductID {
fmt.Print("Informe o Id do produto que deseja deletar: ")
productID, _ := reader.ReadString('\n')
productIDInt, _ := strconv.Atoi(strings.TrimSpace(productID))
return model.ProductID(productIDInt)
}
func ReadNewValueInput(field string) string {
fmt.Printf("Informe o novo valor para %s: ", field)
value, _ := reader.ReadString('\n')
return strings.TrimSpace(value)
}
func ShowAllProducts(products map[model.ProductID]model.Product) {
for _, p := range products {
id := p.ID
name := p.Name
category := p.Category
price := p.Price
quantity := p.Quantity
fmt.Printf(
"==============================================="+"\n"+
"ID PRODUTO: %d"+"\n"+
"NOME: %s"+"\n"+
"CATEGORIA: %s"+"\n"+
"PRICE: %.2f"+"\n"+
"QUANTIDADE: %d"+"\n"+
"==============================================="+"\n",
id, name, category, price, quantity)
}
}