├── .gitignore ├── Dockerfile ├── Dockerfile.prod ├── cmd ├── consumer │ └── main.go ├── exemplo │ └── main.go └── producer │ └── main.go ├── docker-compose.yaml ├── go.mod ├── go.sum ├── internal └── order │ ├── entity │ ├── order.go │ └── order_test.go │ ├── infra │ └── database │ │ └── order_repository.go │ └── usecase │ ├── calculate_price.go │ └── get_total.go ├── k8s ├── deployment.yaml └── service.yaml ├── pkg └── rabbitmq │ └── rabbitmq.go └── prometheus.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .docker/mysql -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.19 2 | WORKDIR /app 3 | ENTRYPOINT [ "tail", "-f", "/dev/null" ] -------------------------------------------------------------------------------- /Dockerfile.prod: -------------------------------------------------------------------------------- 1 | FROM golang:1.19 as builder 2 | WORKDIR /app 3 | COPY . . 4 | RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o server -ldflags="-w -s" cmd/exemplo/main.go 5 | 6 | FROM scratch 7 | COPY --from=builder /app/server /server 8 | CMD [ "/server" ] 9 | -------------------------------------------------------------------------------- /cmd/consumer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "sync" 9 | "time" 10 | 11 | "github.com/devfullcycle/pfa-go/internal/order/infra/database" 12 | "github.com/devfullcycle/pfa-go/internal/order/usecase" 13 | "github.com/devfullcycle/pfa-go/pkg/rabbitmq" 14 | amqp "github.com/rabbitmq/amqp091-go" 15 | ) 16 | 17 | func main() { 18 | maxWorkers := 1 19 | wg := sync.WaitGroup{} 20 | db, err := sql.Open("mysql", "root:root@tcp(mysql:3306)/orders") 21 | if err != nil { 22 | panic(err) 23 | } 24 | defer db.Close() 25 | repository := database.NewOrderRepository(db) 26 | uc := usecase.NewCalculateFinalPriceUseCase(repository) 27 | 28 | http.HandleFunc("/total", func(w http.ResponseWriter, r *http.Request) { 29 | uc := usecase.NewGetTotalUseCase(repository) 30 | output, err := uc.Execute() 31 | if err != nil { 32 | // Internal Server Error 33 | w.WriteHeader(http.StatusInternalServerError) 34 | return 35 | } 36 | json.NewEncoder(w).Encode(output) 37 | }) 38 | go http.ListenAndServe(":8181", nil) 39 | 40 | ch, err := rabbitmq.OpenChannel() 41 | if err != nil { 42 | panic(err) 43 | } 44 | defer ch.Close() 45 | out := make(chan amqp.Delivery) 46 | go rabbitmq.Consume(ch, out) 47 | for i := 0; i < maxWorkers; i++ { 48 | wg.Add(1) 49 | i := i 50 | go func() { 51 | fmt.Println("Starting worker", i) 52 | defer wg.Done() 53 | worker(out, uc, i) 54 | }() 55 | } 56 | wg.Wait() 57 | } 58 | 59 | func worker(deliveryMessage <-chan amqp.Delivery, uc *usecase.CalculateFinalPriceUseCase, workerId int) { 60 | for msg := range deliveryMessage { 61 | var input usecase.OrderInputDTO 62 | err := json.Unmarshal(msg.Body, &input) 63 | if err != nil { 64 | fmt.Println("Error unmarshalling message", err) 65 | } 66 | input.Tax = 10.0 67 | _, err = uc.Execute(input) 68 | if err != nil { 69 | fmt.Println("Error unmarshalling message", err) 70 | } 71 | msg.Ack(false) 72 | fmt.Println("Worker", workerId, "processed order", input.ID) 73 | time.Sleep(1 * time.Second) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /cmd/exemplo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | func main() { 9 | // hello world webserver 10 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 11 | fmt.Fprintf(w, "Hello, world!") 12 | }) 13 | http.ListenAndServe(":8080", nil) 14 | } 15 | -------------------------------------------------------------------------------- /cmd/producer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "math/rand" 6 | 7 | "github.com/google/uuid" 8 | amqp "github.com/rabbitmq/amqp091-go" 9 | ) 10 | 11 | type Order struct { 12 | ID string 13 | Price float64 14 | } 15 | 16 | func GenerateOrders() Order { 17 | return Order{ 18 | ID: uuid.New().String(), 19 | Price: rand.Float64() * 100, 20 | } 21 | } 22 | 23 | func Notify(ch *amqp.Channel, order Order) error { 24 | body, err := json.Marshal(order) 25 | if err != nil { 26 | return err 27 | } 28 | err = ch.Publish( 29 | "amq.direct", // exchange, 30 | "", 31 | false, 32 | false, 33 | amqp.Publishing{ 34 | ContentType: "application/json", 35 | Body: body, 36 | }, 37 | ) 38 | return err 39 | } 40 | 41 | func main() { 42 | conn, err := amqp.Dial("amqp://guest:guest@rabbitmq:5672/") 43 | if err != nil { 44 | panic(err) 45 | } 46 | defer conn.Close() 47 | ch, err := conn.Channel() 48 | if err != nil { 49 | panic(err) 50 | } 51 | defer ch.Close() 52 | for i := 0; i < 100000000; i++ { 53 | order := GenerateOrders() 54 | err := Notify(ch, order) 55 | if err != nil { 56 | panic(err) 57 | } 58 | // fmt.Println(order) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | goapp: 5 | build: . 6 | volumes: 7 | - .:/app 8 | ports: 9 | - "8181:8181" 10 | 11 | mysql: 12 | image: mysql:5.7 13 | ports: 14 | - "3306:3306" 15 | platform: linux/amd64 16 | environment: 17 | - MYSQL_ROOT_PASSWORD=root 18 | - MYSQL_DATABASE=orders 19 | - MYSQL_PASSWORD=root 20 | # volumes: 21 | # - ./.docker/mysql:/var/lib/mysql 22 | 23 | rabbitmq: 24 | image: rabbitmq:3.8.16-management 25 | container_name: rabbitmq 26 | hostname: rabbitmq 27 | ports: 28 | - "5672:5672" 29 | - "15672:15672" 30 | - "15692:15692" 31 | environment: 32 | - RABBITMQ_DEFAULT_USER=guest 33 | - RABBITMQ_DEFAULT_PASS=guest 34 | - RABBITMQ_DEFAULT_VHOST=/ 35 | 36 | prometheus: 37 | image: prom/prometheus 38 | container_name: prometheus 39 | hostname: prometheus 40 | ports: 41 | - "9090:9090" 42 | volumes: 43 | - ./prometheus.yml:/etc/prometheus/prometheus.yml 44 | command: 45 | - '--config.file=/etc/prometheus/prometheus.yml' 46 | - '--storage.tsdb.path=/prometheus' 47 | - '--web.console.libraries=/usr/share/prometheus/console_libraries' 48 | - '--web.console.templates=/usr/share/prometheus/consoles' 49 | 50 | grafanaservice: 51 | image: grafana/grafana 52 | container_name: grafana 53 | hostname: grafana 54 | ports: 55 | - "3000:3000" -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/devfullcycle/pfa-go 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/google/uuid v1.3.0 7 | github.com/rabbitmq/amqp091-go v1.5.0 8 | github.com/stretchr/testify v1.8.0 9 | ) 10 | 11 | require ( 12 | github.com/davecgh/go-spew v1.1.1 // indirect 13 | github.com/go-sql-driver/mysql v1.6.0 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | gopkg.in/yaml.v3 v3.0.1 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= 5 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 6 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 7 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 8 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 9 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 10 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 11 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 12 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 13 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 15 | github.com/rabbitmq/amqp091-go v1.5.0 h1:VouyHPBu1CrKyJVfteGknGOGCzmOz0zcv/tONLkb7rg= 16 | github.com/rabbitmq/amqp091-go v1.5.0/go.mod h1:JsV0ofX5f1nwOGafb8L5rBItt9GyhfQfcJj+oyz0dGg= 17 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 18 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 19 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 20 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 21 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 22 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 23 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 24 | go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= 25 | go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 26 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 27 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 28 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 29 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 30 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 31 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 32 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 33 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 34 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 35 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 36 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 37 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 38 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 39 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 40 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 41 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 42 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 43 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 44 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 45 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 46 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 47 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 48 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 49 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 50 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 51 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 52 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 53 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 54 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 55 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 56 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 57 | -------------------------------------------------------------------------------- /internal/order/entity/order.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "errors" 4 | 5 | type OrderRepositoryInterface interface { 6 | Save(order *Order) error 7 | GetTotal() (int, error) 8 | } 9 | 10 | type Order struct { 11 | ID string 12 | Price float64 13 | Tax float64 14 | FinalPrice float64 15 | } 16 | 17 | func NewOrder(id string, price float64, tax float64) (*Order, error) { 18 | order := &Order{ 19 | ID: id, 20 | Price: price, 21 | Tax: tax, 22 | } 23 | err := order.IsValid() 24 | if err != nil { 25 | return nil, err 26 | } 27 | return order, nil 28 | } 29 | 30 | func (o *Order) CalculateFinalPrice() error { 31 | o.FinalPrice = o.Price + o.Tax 32 | err := o.IsValid() 33 | if err != nil { 34 | return err 35 | } 36 | return nil 37 | } 38 | 39 | func (o Order) IsValid() error { 40 | if o.ID == "" { 41 | return errors.New("invalid id") 42 | } 43 | if o.Price == 0 { 44 | return errors.New("invalid price") 45 | } 46 | if o.Tax == 0 { 47 | return errors.New("invalid tax") 48 | } 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /internal/order/entity/order_test.go: -------------------------------------------------------------------------------- 1 | package entity_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/devfullcycle/pfa-go/internal/order/entity" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestGivenAnEmptyId_WhenCreateANewOrder_ThenShouldReceiveAndError(t *testing.T) { 11 | order := entity.Order{} 12 | assert.Error(t, order.IsValid(), "invalid id") 13 | } 14 | 15 | func TestGivenAnEmptyPrice_WhenCreateANewOrder_ThenShouldReceiveAndError(t *testing.T) { 16 | order := entity.Order{ID: "123"} 17 | assert.Error(t, order.IsValid(), "invalid price") 18 | } 19 | 20 | func TestGivenAnEmptyTax_WhenCreateANewOrder_ThenShouldReceiveAndError(t *testing.T) { 21 | order := entity.Order{ID: "123", Price: 10} 22 | assert.Error(t, order.IsValid(), "invalid tax") 23 | } 24 | 25 | func TestGivenAValidParams_WhenCallNewOrder_ThenShould_ReceiveCreateOrderWithAllParams(t *testing.T) { 26 | order, err := entity.NewOrder("123", 10, 2) 27 | assert.NoError(t, err) 28 | assert.Equal(t, "123", order.ID) 29 | assert.Equal(t, 10.0, order.Price) 30 | assert.Equal(t, 2.0, order.Tax) 31 | } 32 | 33 | func TestGivenAValidParams_WhenCallCalculateFinalPrice_ThenShouldCalculateFinalPriceAndSetItOnFinalPriceProperty(t *testing.T) { 34 | order, err := entity.NewOrder("123", 10, 2) 35 | assert.NoError(t, err) 36 | err = order.CalculateFinalPrice() 37 | assert.NoError(t, err) 38 | assert.Equal(t, 12.0, order.FinalPrice) 39 | } 40 | -------------------------------------------------------------------------------- /internal/order/infra/database/order_repository.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "database/sql" 5 | 6 | "github.com/devfullcycle/pfa-go/internal/order/entity" 7 | _ "github.com/go-sql-driver/mysql" 8 | ) 9 | 10 | type OrderRepository struct { 11 | Db *sql.DB 12 | } 13 | 14 | func NewOrderRepository(db *sql.DB) *OrderRepository { 15 | return &OrderRepository{Db: db} 16 | } 17 | 18 | // sql create table on mysql 19 | // CREATE TABLE `orders` ( 20 | // `id` varchar(255) NOT NULL, 21 | // `price` float NOT NULL, 22 | // `tax` float NOT NULL, 23 | // `final_price` float NOT NULL, 24 | // PRIMARY KEY (`id`)) 25 | // ) 26 | 27 | func (r *OrderRepository) Save(order *entity.Order) error { 28 | stmt, err := r.Db.Prepare("INSERT INTO orders (id, price, tax, final_price) VALUES (?, ?, ?, ?)") 29 | if err != nil { 30 | return err 31 | } 32 | _, err = stmt.Exec(order.ID, order.Price, order.Tax, order.FinalPrice) 33 | if err != nil { 34 | return err 35 | } 36 | return nil 37 | } 38 | 39 | func (r *OrderRepository) GetTotal() (int, error) { 40 | var total int 41 | err := r.Db.QueryRow("SELECT COUNT(*) FROM orders").Scan(&total) 42 | if err != nil { 43 | return 0, err 44 | } 45 | return total, nil 46 | } 47 | -------------------------------------------------------------------------------- /internal/order/usecase/calculate_price.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import "github.com/devfullcycle/pfa-go/internal/order/entity" 4 | 5 | type OrderInputDTO struct { 6 | ID string 7 | Price float64 8 | Tax float64 9 | } 10 | 11 | type OrderOutputDTO struct { 12 | ID string 13 | Price float64 14 | Tax float64 15 | FinalPrice float64 16 | } 17 | 18 | type CalculateFinalPriceUseCase struct { 19 | OrderRepository entity.OrderRepositoryInterface 20 | } 21 | 22 | func NewCalculateFinalPriceUseCase(orderRepository entity.OrderRepositoryInterface) *CalculateFinalPriceUseCase { 23 | return &CalculateFinalPriceUseCase{OrderRepository: orderRepository} 24 | } 25 | 26 | func (c *CalculateFinalPriceUseCase) Execute(input OrderInputDTO) (*OrderOutputDTO, error) { 27 | order, err := entity.NewOrder(input.ID, input.Price, input.Tax) 28 | if err != nil { 29 | return nil, err 30 | } 31 | err = order.CalculateFinalPrice() 32 | if err != nil { 33 | return nil, err 34 | } 35 | err = c.OrderRepository.Save(order) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return &OrderOutputDTO{ 40 | ID: order.ID, 41 | Price: order.Price, 42 | Tax: order.Tax, 43 | FinalPrice: order.FinalPrice, 44 | }, nil 45 | } 46 | -------------------------------------------------------------------------------- /internal/order/usecase/get_total.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "github.com/devfullcycle/pfa-go/internal/order/entity" 5 | ) 6 | 7 | type GetTotalOutputDto struct { 8 | Total int 9 | } 10 | 11 | type GetTotalUseCase struct { 12 | OrderRepository entity.OrderRepositoryInterface 13 | } 14 | 15 | func NewGetTotalUseCase(orderRepository entity.OrderRepositoryInterface) *GetTotalUseCase { 16 | return &GetTotalUseCase{OrderRepository: orderRepository} 17 | } 18 | 19 | func (c *GetTotalUseCase) Execute() (*GetTotalOutputDto, error) { 20 | total, err := c.OrderRepository.GetTotal() 21 | if err != nil { 22 | return nil, err 23 | } 24 | return &GetTotalOutputDto{Total: total}, nil 25 | } 26 | -------------------------------------------------------------------------------- /k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: goserver 5 | spec: 6 | replicas: 10 7 | selector: 8 | matchLabels: 9 | app: goserver 10 | template: 11 | metadata: 12 | labels: 13 | app: goserver 14 | spec: 15 | containers: 16 | - name: goserver 17 | image: wesleywillians/pfa-go:latest 18 | ports: 19 | - containerPort: 8080 20 | -------------------------------------------------------------------------------- /k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: goservice 5 | spec: 6 | type: LoadBalancer 7 | selector: 8 | app: goserver 9 | ports: 10 | - port: 8080 11 | targetPort: 8080 12 | -------------------------------------------------------------------------------- /pkg/rabbitmq/rabbitmq.go: -------------------------------------------------------------------------------- 1 | package rabbitmq 2 | 3 | import amqp "github.com/rabbitmq/amqp091-go" 4 | 5 | func OpenChannel() (*amqp.Channel, error) { 6 | conn, err := amqp.Dial("amqp://guest:guest@rabbitmq:5672/") 7 | if err != nil { 8 | panic(err) 9 | } 10 | ch, err := conn.Channel() 11 | ch.Qos(100, 0, false) 12 | if err != nil { 13 | panic(err) 14 | } 15 | return ch, nil 16 | } 17 | 18 | func Consume(ch *amqp.Channel, out chan amqp.Delivery) error { 19 | msgs, err := ch.Consume( 20 | "orders", 21 | "go-consumer", 22 | false, 23 | false, 24 | false, 25 | false, 26 | nil, 27 | ) 28 | if err != nil { 29 | return err 30 | } 31 | for msg := range msgs { 32 | out <- msg 33 | } 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 5s # By default, scrape targets every 15 seconds. 3 | 4 | # A scrape configuration containing exactly one endpoint to scrape: 5 | # Here it's Prometheus itself. 6 | scrape_configs: 7 | # The job name is added as a label `job=` to any timeseries scraped from this config. 8 | - job_name: 'rabbitmq' 9 | # Override the global default and scrape targets from this job every 5 seconds. 10 | static_configs: 11 | - targets: ['rabbitmq:15692'] 12 | 13 | # - job_name: 'rabbitmq' 14 | # # Override the global default and scrape targets from this job every 5 seconds. 15 | # scrape_interval: 5s 16 | # static_configs: 17 | # - targets: ['rabbitmq:15692'] --------------------------------------------------------------------------------