snapshot of https://github.com/ThreeDotsLabs/watermill.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
master / ci (push) Failing after 0s
Some checks failed
master / ci (push) Failing after 0s
This commit is contained in:
6
_examples/basic/5-cqrs-protobuf/.validate_example.yml
Normal file
6
_examples/basic/5-cqrs-protobuf/.validate_example.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
validation_cmd: "docker compose up"
|
||||
teardown_cmd: "docker compose down"
|
||||
timeout: 120
|
||||
expected_outputs:
|
||||
- "beers ordered to room \\d+"
|
||||
- "Already booked rooms for \\$\\d{2,}"
|
||||
4
_examples/basic/5-cqrs-protobuf/Makefile
Normal file
4
_examples/basic/5-cqrs-protobuf/Makefile
Normal file
@@ -0,0 +1,4 @@
|
||||
.PHONY: proto
|
||||
|
||||
proto:
|
||||
protoc --proto_path=proto --go_out=. --go_opt=paths=source_relative proto/messages.proto
|
||||
42
_examples/basic/5-cqrs-protobuf/README.md
Normal file
42
_examples/basic/5-cqrs-protobuf/README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Example Golang CQRS application
|
||||
|
||||
This application is using [Watermill CQRS](http://watermill.io/docs/cqrs) component.
|
||||
|
||||
Detailed documentation for CQRS can be found in Watermill's docs: [http://watermill.io/docs/cqrs#usage](http://watermill.io/docs/cqrs).
|
||||
|
||||

|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant M as Main
|
||||
participant CB as CommandBus
|
||||
participant BRH as BookRoomHandler
|
||||
participant EB as EventBus
|
||||
participant OBRB as OrderBeerOnRoomBooked
|
||||
participant OBH as OrderBeerHandler
|
||||
participant BFR as BookingsFinancialReport
|
||||
|
||||
Note over M,BFR: Commands use AMQP queue, Events use AMQP pub/sub
|
||||
|
||||
M->>CB: Send(BookRoom)<br/>topic: commands.BookRoom
|
||||
CB->>BRH: Handle(BookRoom)
|
||||
|
||||
BRH->>EB: Publish(RoomBooked)<br/>topic: events.RoomBooked
|
||||
|
||||
par Process RoomBooked Event
|
||||
EB->>OBRB: Handle(RoomBooked)
|
||||
OBRB->>CB: Send(OrderBeer)<br/>topic: commands.OrderBeer
|
||||
CB->>OBH: Handle(OrderBeer)
|
||||
OBH->>EB: Publish(BeerOrdered)<br/>topic: events.BeerOrdered
|
||||
|
||||
EB->>BFR: Handle(RoomBooked)
|
||||
Note over BFR: Updates financial report
|
||||
end
|
||||
```
|
||||
|
||||
|
||||
## Running
|
||||
|
||||
```bash
|
||||
docker-compose up
|
||||
```
|
||||
20
_examples/basic/5-cqrs-protobuf/docker-compose.yml
Normal file
20
_examples/basic/5-cqrs-protobuf/docker-compose.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
services:
|
||||
golang:
|
||||
image: golang:1.25
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8080:8080
|
||||
depends_on:
|
||||
- rabbitmq
|
||||
links:
|
||||
- rabbitmq
|
||||
volumes:
|
||||
- .:/app
|
||||
- $GOPATH/pkg/mod:/go/pkg/mod
|
||||
working_dir: /app
|
||||
command: go run .
|
||||
|
||||
rabbitmq:
|
||||
image: rabbitmq:3.7
|
||||
restart: unless-stopped
|
||||
attach: false
|
||||
23
_examples/basic/5-cqrs-protobuf/go.mod
Normal file
23
_examples/basic/5-cqrs-protobuf/go.mod
Normal file
@@ -0,0 +1,23 @@
|
||||
module main.go
|
||||
|
||||
require (
|
||||
github.com/ThreeDotsLabs/watermill v1.5.1
|
||||
github.com/ThreeDotsLabs/watermill-amqp/v3 v3.0.2
|
||||
google.golang.org/protobuf v1.36.11
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/rabbitmq/amqp091-go v1.11.0 // indirect
|
||||
github.com/sony/gobreaker v1.0.0 // indirect
|
||||
)
|
||||
|
||||
go 1.25
|
||||
76
_examples/basic/5-cqrs-protobuf/go.sum
Normal file
76
_examples/basic/5-cqrs-protobuf/go.sum
Normal file
@@ -0,0 +1,76 @@
|
||||
github.com/ThreeDotsLabs/watermill v1.5.1 h1:t5xMivyf9tpmU3iozPqyrCZXHvoV1XQDfihas4sV0fY=
|
||||
github.com/ThreeDotsLabs/watermill v1.5.1/go.mod h1:Uop10dA3VeJWsSvis9qO3vbVY892LARrKAdki6WtXS4=
|
||||
github.com/ThreeDotsLabs/watermill-amqp/v3 v3.0.2 h1:aeyFSR4SUsbszmocuFiYY13nsHorc6CXIS2Hy7+xgFU=
|
||||
github.com/ThreeDotsLabs/watermill-amqp/v3 v3.0.2/go.mod h1:+8tCh6VCuBcQWhfETCwzRINKQ1uyeg9moH3h7jMKxQk=
|
||||
github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M=
|
||||
github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts=
|
||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rabbitmq/amqp091-go v1.11.0 h1:HxIctVm9Gid/Vtn706necmZ7Wj6pgGI2eqplRbEY8O8=
|
||||
github.com/rabbitmq/amqp091-go v1.11.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
|
||||
github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ=
|
||||
github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
|
||||
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
351
_examples/basic/5-cqrs-protobuf/main.go
Normal file
351
_examples/basic/5-cqrs-protobuf/main.go
Normal file
@@ -0,0 +1,351 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ThreeDotsLabs/watermill"
|
||||
"github.com/ThreeDotsLabs/watermill-amqp/v3/pkg/amqp"
|
||||
"github.com/ThreeDotsLabs/watermill/components/cqrs"
|
||||
"github.com/ThreeDotsLabs/watermill/message"
|
||||
"github.com/ThreeDotsLabs/watermill/message/router/middleware"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
// BookRoomHandler is a command handler, which handles BookRoom command and emits RoomBooked.
|
||||
//
|
||||
// In CQRS, one command must be handled by only one handler.
|
||||
// When another handler with this command is added to command processor, error will be returned.
|
||||
type BookRoomHandler struct {
|
||||
eventBus *cqrs.EventBus
|
||||
}
|
||||
|
||||
func (b BookRoomHandler) Handle(ctx context.Context, cmd *BookRoom) error {
|
||||
// some random price, in production you probably will calculate in wiser way
|
||||
price := (rand.Int63n(40) + 1) * 10
|
||||
|
||||
slog.Info(
|
||||
"Booked room",
|
||||
"room_id", cmd.RoomId,
|
||||
"guest_name", cmd.GuestName,
|
||||
"start_date", time.Unix(cmd.StartDate.Seconds, int64(cmd.StartDate.Nanos)),
|
||||
"end_date", time.Unix(cmd.EndDate.Seconds, int64(cmd.EndDate.Nanos)),
|
||||
)
|
||||
|
||||
// RoomBooked will be handled by OrderBeerOnRoomBooked event handler,
|
||||
// in future RoomBooked may be handled by multiple event handler
|
||||
if err := b.eventBus.Publish(ctx, &RoomBooked{
|
||||
ReservationId: watermill.NewUUID(),
|
||||
RoomId: cmd.RoomId,
|
||||
GuestName: cmd.GuestName,
|
||||
Price: price,
|
||||
StartDate: cmd.StartDate,
|
||||
EndDate: cmd.EndDate,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OrderBeerOnRoomBooked is an event handler, which handles RoomBooked event and emits OrderBeer command.
|
||||
type OrderBeerOnRoomBooked struct {
|
||||
commandBus *cqrs.CommandBus
|
||||
}
|
||||
|
||||
func (o OrderBeerOnRoomBooked) Handle(ctx context.Context, event *RoomBooked) error {
|
||||
orderBeerCmd := &OrderBeer{
|
||||
RoomId: event.RoomId,
|
||||
Count: rand.Int63n(10) + 1,
|
||||
}
|
||||
|
||||
return o.commandBus.Send(ctx, orderBeerCmd)
|
||||
}
|
||||
|
||||
// OrderBeerHandler is a command handler, which handles OrderBeer command and emits BeerOrdered.
|
||||
// BeerOrdered is not handled by any event handler, but we may use persistent Pub/Sub to handle it in the future.
|
||||
type OrderBeerHandler struct {
|
||||
eventBus *cqrs.EventBus
|
||||
}
|
||||
|
||||
func (o OrderBeerHandler) Handle(ctx context.Context, cmd *OrderBeer) error {
|
||||
if rand.Int63n(10) == 0 {
|
||||
// sometimes there is no beer left, command will be retried
|
||||
return fmt.Errorf("no beer left for room %s, please try later", cmd.RoomId)
|
||||
}
|
||||
|
||||
if err := o.eventBus.Publish(ctx, &BeerOrdered{
|
||||
RoomId: cmd.RoomId,
|
||||
Count: cmd.Count,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
slog.Info(fmt.Sprintf("%d beers ordered to room %s", cmd.Count, cmd.RoomId))
|
||||
return nil
|
||||
}
|
||||
|
||||
// BookingsFinancialReport is a read model, which calculates how much money we may earn from bookings.
|
||||
// Like OrderBeerOnRoomBooked, it listens for RoomBooked event.
|
||||
//
|
||||
// This implementation is just writing to the memory. In production, you will probably use some persistent storage.
|
||||
type BookingsFinancialReport struct {
|
||||
handledBookings map[string]struct{}
|
||||
totalCharge int64
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func NewBookingsFinancialReport() *BookingsFinancialReport {
|
||||
return &BookingsFinancialReport{handledBookings: map[string]struct{}{}}
|
||||
}
|
||||
|
||||
func (b *BookingsFinancialReport) Handle(ctx context.Context, event *RoomBooked) error {
|
||||
// Handle may be called concurrently, so it needs to be thread safe.
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
// When we are using Pub/Sub which doesn't provide exactly-once delivery semantics, we need to deduplicate messages.
|
||||
// GoChannel Pub/Sub provides exactly-once delivery,
|
||||
// but let's make this example ready for other Pub/Sub implementations.
|
||||
if _, ok := b.handledBookings[event.ReservationId]; ok {
|
||||
return nil
|
||||
}
|
||||
b.handledBookings[event.ReservationId] = struct{}{}
|
||||
|
||||
b.totalCharge += event.Price
|
||||
|
||||
slog.Info(fmt.Sprintf(">>> Already booked rooms for $%d\n", b.totalCharge))
|
||||
return nil
|
||||
}
|
||||
|
||||
var amqpAddress = "amqp://guest:guest@rabbitmq:5672/"
|
||||
|
||||
func main() {
|
||||
logger := watermill.NewSlogLoggerWithLevelMapping(nil, map[slog.Level]slog.Level{
|
||||
slog.LevelInfo: slog.LevelDebug,
|
||||
})
|
||||
|
||||
cqrsMarshaler := cqrs.ProtoMarshaler{
|
||||
// It will generate topic names based on the event/command type.
|
||||
// So for example, for "RoomBooked" name will be "RoomBooked".
|
||||
//
|
||||
// This value is used to generate topic names with "generateEventsTopic" and "generateCommandsTopic" functions.
|
||||
GenerateName: cqrs.StructName,
|
||||
}
|
||||
|
||||
generateEventsTopic := func(eventName string) string {
|
||||
return "events." + eventName
|
||||
}
|
||||
|
||||
generateCommandsTopic := func(commandName string) string {
|
||||
return "commands." + commandName
|
||||
}
|
||||
|
||||
// You can use any Pub/Sub implementation from here: https://watermill.io/pubsubs/
|
||||
// Detailed RabbitMQ implementation: https://watermill.io/pubsubs/amqp/
|
||||
// Commands will be sent to queue, because they need to be consumed once.
|
||||
commandsAMQPConfig := amqp.NewDurableQueueConfig(amqpAddress)
|
||||
commandsPublisher, err := amqp.NewPublisher(commandsAMQPConfig, logger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
commandsSubscriber, err := amqp.NewSubscriber(commandsAMQPConfig, logger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Events will be published to PubSub configured Rabbit, because they may be consumed by multiple consumers.
|
||||
// (in that case BookingsFinancialReport and OrderBeerOnRoomBooked).
|
||||
eventsPublisher, err := amqp.NewPublisher(amqp.NewDurablePubSubConfig(amqpAddress, nil), logger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// CQRS is built on messages router. Detailed documentation: https://watermill.io/docs/messages-router/
|
||||
router, err := message.NewRouter(message.RouterConfig{}, logger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Simple middleware which will recover panics from event or command handlers.
|
||||
// More about router middlewares you can find in the documentation:
|
||||
// https://watermill.io/docs/messages-router/#middleware
|
||||
//
|
||||
// List of available middlewares you can find in message/router/middleware.
|
||||
router.AddMiddleware(middleware.Recoverer)
|
||||
|
||||
commandBus, err := cqrs.NewCommandBusWithConfig(commandsPublisher, cqrs.CommandBusConfig{
|
||||
GeneratePublishTopic: func(params cqrs.CommandBusGeneratePublishTopicParams) (string, error) {
|
||||
return generateCommandsTopic(params.CommandName), nil
|
||||
},
|
||||
OnSend: func(params cqrs.CommandBusOnSendParams) error {
|
||||
logger.Info("Sending command", watermill.LogFields{
|
||||
"command_name": params.CommandName,
|
||||
})
|
||||
|
||||
params.Message.Metadata.Set("sent_at", time.Now().String())
|
||||
|
||||
return nil
|
||||
},
|
||||
Marshaler: cqrsMarshaler,
|
||||
Logger: logger,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
commandProcessor, err := cqrs.NewCommandProcessorWithConfig(
|
||||
router,
|
||||
cqrs.CommandProcessorConfig{
|
||||
GenerateSubscribeTopic: func(params cqrs.CommandProcessorGenerateSubscribeTopicParams) (string, error) {
|
||||
return generateCommandsTopic(params.CommandName), nil
|
||||
},
|
||||
SubscriberConstructor: func(params cqrs.CommandProcessorSubscriberConstructorParams) (message.Subscriber, error) {
|
||||
// we can reuse subscriber, because all commands have separated topics
|
||||
return commandsSubscriber, nil
|
||||
},
|
||||
OnHandle: func(params cqrs.CommandProcessorOnHandleParams) error {
|
||||
start := time.Now()
|
||||
|
||||
err := params.Handler.Handle(params.Message.Context(), params.Command)
|
||||
|
||||
logger.Info("Command handled", watermill.LogFields{
|
||||
"command_name": params.CommandName,
|
||||
"duration": time.Since(start),
|
||||
"err": err,
|
||||
})
|
||||
|
||||
return err
|
||||
},
|
||||
Marshaler: cqrsMarshaler,
|
||||
Logger: logger,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
eventBus, err := cqrs.NewEventBusWithConfig(eventsPublisher, cqrs.EventBusConfig{
|
||||
GeneratePublishTopic: func(params cqrs.GenerateEventPublishTopicParams) (string, error) {
|
||||
return generateEventsTopic(params.EventName), nil
|
||||
},
|
||||
|
||||
OnPublish: func(params cqrs.OnEventSendParams) error {
|
||||
logger.Info("Publishing event", watermill.LogFields{
|
||||
"event_name": params.EventName,
|
||||
})
|
||||
|
||||
params.Message.Metadata.Set("published_at", time.Now().String())
|
||||
|
||||
return nil
|
||||
},
|
||||
|
||||
Marshaler: cqrsMarshaler,
|
||||
Logger: logger,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
eventProcessor, err := cqrs.NewEventProcessorWithConfig(
|
||||
router,
|
||||
cqrs.EventProcessorConfig{
|
||||
GenerateSubscribeTopic: func(params cqrs.EventProcessorGenerateSubscribeTopicParams) (string, error) {
|
||||
return generateEventsTopic(params.EventName), nil
|
||||
},
|
||||
SubscriberConstructor: func(params cqrs.EventProcessorSubscriberConstructorParams) (message.Subscriber, error) {
|
||||
config := amqp.NewDurablePubSubConfig(
|
||||
amqpAddress,
|
||||
amqp.GenerateQueueNameTopicNameWithSuffix(params.HandlerName),
|
||||
)
|
||||
|
||||
return amqp.NewSubscriber(config, logger)
|
||||
},
|
||||
|
||||
OnHandle: func(params cqrs.EventProcessorOnHandleParams) error {
|
||||
start := time.Now()
|
||||
|
||||
err := params.Handler.Handle(params.Message.Context(), params.Event)
|
||||
|
||||
logger.Info("Event handled", watermill.LogFields{
|
||||
"event_name": params.EventName,
|
||||
"duration": time.Since(start),
|
||||
"err": err,
|
||||
})
|
||||
|
||||
return err
|
||||
},
|
||||
|
||||
Marshaler: cqrsMarshaler,
|
||||
Logger: logger,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = commandProcessor.AddHandlers(
|
||||
cqrs.NewCommandHandler("BookRoomHandler", BookRoomHandler{eventBus}.Handle),
|
||||
cqrs.NewCommandHandler("OrderBeerHandler", OrderBeerHandler{eventBus}.Handle),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = eventProcessor.AddHandlers(
|
||||
cqrs.NewEventHandler(
|
||||
"OrderBeerOnRoomBooked",
|
||||
OrderBeerOnRoomBooked{commandBus}.Handle,
|
||||
),
|
||||
cqrs.NewEventHandler(
|
||||
"LogBeerOrdered",
|
||||
func(ctx context.Context, event *BeerOrdered) error {
|
||||
logger.Info("Beer ordered", watermill.LogFields{
|
||||
"room_id": event.RoomId,
|
||||
})
|
||||
return nil
|
||||
},
|
||||
),
|
||||
cqrs.NewEventHandler(
|
||||
"BookingsFinancialReport",
|
||||
NewBookingsFinancialReport().Handle,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// publish BookRoom commands every second to simulate incoming traffic
|
||||
go publishCommands(commandBus)
|
||||
|
||||
// processors are based on router, so they will work when router will start
|
||||
if err := router.Run(context.Background()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func publishCommands(commandBus *cqrs.CommandBus) func() {
|
||||
i := 0
|
||||
for {
|
||||
i++
|
||||
|
||||
startDate := timestamppb.New(time.Now())
|
||||
endDate := timestamppb.New(time.Now().Add(time.Hour * 24 * 3))
|
||||
|
||||
bookRoomCmd := &BookRoom{
|
||||
RoomId: fmt.Sprintf("%d", i),
|
||||
GuestName: "John",
|
||||
StartDate: startDate,
|
||||
EndDate: endDate,
|
||||
}
|
||||
if err := commandBus.Send(context.Background(), bookRoomCmd); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
441
_examples/basic/5-cqrs-protobuf/messages.pb.go
Normal file
441
_examples/basic/5-cqrs-protobuf/messages.pb.go
Normal file
@@ -0,0 +1,441 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.31.0
|
||||
// protoc v4.24.4
|
||||
// source: messages.proto
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type BookRoom struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
RoomId string `protobuf:"bytes,1,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"`
|
||||
GuestName string `protobuf:"bytes,2,opt,name=guest_name,json=guestName,proto3" json:"guest_name,omitempty"`
|
||||
StartDate *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=start_date,json=startDate,proto3" json:"start_date,omitempty"`
|
||||
EndDate *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=end_date,json=endDate,proto3" json:"end_date,omitempty"`
|
||||
}
|
||||
|
||||
func (x *BookRoom) Reset() {
|
||||
*x = BookRoom{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_messages_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *BookRoom) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*BookRoom) ProtoMessage() {}
|
||||
|
||||
func (x *BookRoom) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_messages_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use BookRoom.ProtoReflect.Descriptor instead.
|
||||
func (*BookRoom) Descriptor() ([]byte, []int) {
|
||||
return file_messages_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *BookRoom) GetRoomId() string {
|
||||
if x != nil {
|
||||
return x.RoomId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *BookRoom) GetGuestName() string {
|
||||
if x != nil {
|
||||
return x.GuestName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *BookRoom) GetStartDate() *timestamppb.Timestamp {
|
||||
if x != nil {
|
||||
return x.StartDate
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *BookRoom) GetEndDate() *timestamppb.Timestamp {
|
||||
if x != nil {
|
||||
return x.EndDate
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type RoomBooked struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
ReservationId string `protobuf:"bytes,1,opt,name=reservation_id,json=reservationId,proto3" json:"reservation_id,omitempty"`
|
||||
RoomId string `protobuf:"bytes,2,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"`
|
||||
GuestName string `protobuf:"bytes,3,opt,name=guest_name,json=guestName,proto3" json:"guest_name,omitempty"`
|
||||
Price int64 `protobuf:"varint,4,opt,name=price,proto3" json:"price,omitempty"`
|
||||
StartDate *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=start_date,json=startDate,proto3" json:"start_date,omitempty"`
|
||||
EndDate *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=end_date,json=endDate,proto3" json:"end_date,omitempty"`
|
||||
}
|
||||
|
||||
func (x *RoomBooked) Reset() {
|
||||
*x = RoomBooked{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_messages_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *RoomBooked) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*RoomBooked) ProtoMessage() {}
|
||||
|
||||
func (x *RoomBooked) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_messages_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use RoomBooked.ProtoReflect.Descriptor instead.
|
||||
func (*RoomBooked) Descriptor() ([]byte, []int) {
|
||||
return file_messages_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *RoomBooked) GetReservationId() string {
|
||||
if x != nil {
|
||||
return x.ReservationId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RoomBooked) GetRoomId() string {
|
||||
if x != nil {
|
||||
return x.RoomId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RoomBooked) GetGuestName() string {
|
||||
if x != nil {
|
||||
return x.GuestName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RoomBooked) GetPrice() int64 {
|
||||
if x != nil {
|
||||
return x.Price
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *RoomBooked) GetStartDate() *timestamppb.Timestamp {
|
||||
if x != nil {
|
||||
return x.StartDate
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *RoomBooked) GetEndDate() *timestamppb.Timestamp {
|
||||
if x != nil {
|
||||
return x.EndDate
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type OrderBeer struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
RoomId string `protobuf:"bytes,1,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"`
|
||||
Count int64 `protobuf:"varint,2,opt,name=count,proto3" json:"count,omitempty"`
|
||||
}
|
||||
|
||||
func (x *OrderBeer) Reset() {
|
||||
*x = OrderBeer{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_messages_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *OrderBeer) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*OrderBeer) ProtoMessage() {}
|
||||
|
||||
func (x *OrderBeer) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_messages_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use OrderBeer.ProtoReflect.Descriptor instead.
|
||||
func (*OrderBeer) Descriptor() ([]byte, []int) {
|
||||
return file_messages_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *OrderBeer) GetRoomId() string {
|
||||
if x != nil {
|
||||
return x.RoomId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *OrderBeer) GetCount() int64 {
|
||||
if x != nil {
|
||||
return x.Count
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type BeerOrdered struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
RoomId string `protobuf:"bytes,1,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"`
|
||||
Count int64 `protobuf:"varint,2,opt,name=count,proto3" json:"count,omitempty"`
|
||||
}
|
||||
|
||||
func (x *BeerOrdered) Reset() {
|
||||
*x = BeerOrdered{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_messages_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *BeerOrdered) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*BeerOrdered) ProtoMessage() {}
|
||||
|
||||
func (x *BeerOrdered) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_messages_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use BeerOrdered.ProtoReflect.Descriptor instead.
|
||||
func (*BeerOrdered) Descriptor() ([]byte, []int) {
|
||||
return file_messages_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *BeerOrdered) GetRoomId() string {
|
||||
if x != nil {
|
||||
return x.RoomId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *BeerOrdered) GetCount() int64 {
|
||||
if x != nil {
|
||||
return x.Count
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var File_messages_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_messages_proto_rawDesc = []byte{
|
||||
0x0a, 0x0e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x12, 0x04, 0x6d, 0x61, 0x69, 0x6e, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
|
||||
0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb4, 0x01, 0x0a, 0x08, 0x42, 0x6f, 0x6f, 0x6b,
|
||||
0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1d, 0x0a,
|
||||
0x0a, 0x67, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x09, 0x67, 0x75, 0x65, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x0a,
|
||||
0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x73, 0x74,
|
||||
0x61, 0x72, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64,
|
||||
0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
||||
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65,
|
||||
0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x44, 0x61, 0x74, 0x65, 0x22, 0xf3,
|
||||
0x01, 0x0a, 0x0a, 0x52, 0x6f, 0x6f, 0x6d, 0x42, 0x6f, 0x6f, 0x6b, 0x65, 0x64, 0x12, 0x25, 0x0a,
|
||||
0x0e, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x61, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x1d, 0x0a,
|
||||
0x0a, 0x67, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x09, 0x67, 0x75, 0x65, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05,
|
||||
0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x70, 0x72, 0x69,
|
||||
0x63, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65,
|
||||
0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
|
||||
0x6d, 0x70, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a,
|
||||
0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65, 0x6e, 0x64,
|
||||
0x44, 0x61, 0x74, 0x65, 0x22, 0x3a, 0x0a, 0x09, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x42, 0x65, 0x65,
|
||||
0x72, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f,
|
||||
0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74,
|
||||
0x22, 0x3c, 0x0a, 0x0b, 0x42, 0x65, 0x65, 0x72, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x12,
|
||||
0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e,
|
||||
0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x42, 0x08,
|
||||
0x5a, 0x06, 0x2e, 0x2f, 0x6d, 0x61, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_messages_proto_rawDescOnce sync.Once
|
||||
file_messages_proto_rawDescData = file_messages_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_messages_proto_rawDescGZIP() []byte {
|
||||
file_messages_proto_rawDescOnce.Do(func() {
|
||||
file_messages_proto_rawDescData = protoimpl.X.CompressGZIP(file_messages_proto_rawDescData)
|
||||
})
|
||||
return file_messages_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||
var file_messages_proto_goTypes = []interface{}{
|
||||
(*BookRoom)(nil), // 0: main.BookRoom
|
||||
(*RoomBooked)(nil), // 1: main.RoomBooked
|
||||
(*OrderBeer)(nil), // 2: main.OrderBeer
|
||||
(*BeerOrdered)(nil), // 3: main.BeerOrdered
|
||||
(*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp
|
||||
}
|
||||
var file_messages_proto_depIdxs = []int32{
|
||||
4, // 0: main.BookRoom.start_date:type_name -> google.protobuf.Timestamp
|
||||
4, // 1: main.BookRoom.end_date:type_name -> google.protobuf.Timestamp
|
||||
4, // 2: main.RoomBooked.start_date:type_name -> google.protobuf.Timestamp
|
||||
4, // 3: main.RoomBooked.end_date:type_name -> google.protobuf.Timestamp
|
||||
4, // [4:4] is the sub-list for method output_type
|
||||
4, // [4:4] is the sub-list for method input_type
|
||||
4, // [4:4] is the sub-list for extension type_name
|
||||
4, // [4:4] is the sub-list for extension extendee
|
||||
0, // [0:4] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_messages_proto_init() }
|
||||
func file_messages_proto_init() {
|
||||
if File_messages_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_messages_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*BookRoom); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_messages_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*RoomBooked); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_messages_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*OrderBeer); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_messages_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*BeerOrdered); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_messages_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 4,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_messages_proto_goTypes,
|
||||
DependencyIndexes: file_messages_proto_depIdxs,
|
||||
MessageInfos: file_messages_proto_msgTypes,
|
||||
}.Build()
|
||||
File_messages_proto = out.File
|
||||
file_messages_proto_rawDesc = nil
|
||||
file_messages_proto_goTypes = nil
|
||||
file_messages_proto_depIdxs = nil
|
||||
}
|
||||
35
_examples/basic/5-cqrs-protobuf/proto/messages.proto
Normal file
35
_examples/basic/5-cqrs-protobuf/proto/messages.proto
Normal file
@@ -0,0 +1,35 @@
|
||||
syntax = "proto3";
|
||||
package main;
|
||||
|
||||
option go_package = "./main";
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
message BookRoom {
|
||||
string room_id = 1;
|
||||
string guest_name = 2;
|
||||
|
||||
google.protobuf.Timestamp start_date = 4;
|
||||
google.protobuf.Timestamp end_date = 5;
|
||||
}
|
||||
|
||||
message RoomBooked {
|
||||
string reservation_id = 1;
|
||||
string room_id = 2;
|
||||
string guest_name = 3;
|
||||
int64 price = 4;
|
||||
|
||||
google.protobuf.Timestamp start_date = 5;
|
||||
google.protobuf.Timestamp end_date = 6;
|
||||
}
|
||||
|
||||
message OrderBeer {
|
||||
string room_id = 1;
|
||||
int64 count = 2;
|
||||
}
|
||||
|
||||
|
||||
message BeerOrdered {
|
||||
string room_id = 1;
|
||||
int64 count = 2;
|
||||
}
|
||||
Reference in New Issue
Block a user