test(utils): 添加事件驱动模型测试
- 在 sqrt_test.go 中添加了 fastSqr1 测试函数,用于测试事件驱动模型 - 新增了 Event 和 Uint32AsyncEvent 类型用于测试 - 更新了 go.work、go.mod 和
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
package cart
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/badu/bus"
|
||||
"github.com/badu/bus/test_scenarios/factory-request-reply/events"
|
||||
"github.com/badu/bus/test_scenarios/factory-request-reply/inventory"
|
||||
"github.com/badu/bus/test_scenarios/factory-request-reply/prices"
|
||||
)
|
||||
|
||||
type ServiceImpl struct {
|
||||
sb *strings.Builder
|
||||
}
|
||||
|
||||
func NewService(sb *strings.Builder) ServiceImpl {
|
||||
result := ServiceImpl{sb: sb}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) AddProductToCart(ctx context.Context, productID string) error {
|
||||
inventoryClientRequest := events.NewInventoryGRPCClientRequestEvent()
|
||||
bus.Pub(inventoryClientRequest)
|
||||
inventoryClientRequest.WaitReply()
|
||||
|
||||
pricesClientRequest := events.NewPricesGRPCClientRequestEvent()
|
||||
bus.Pub(pricesClientRequest)
|
||||
pricesClientRequest.WaitReply()
|
||||
|
||||
defer inventoryClientRequest.Conn.Close() // close GRPC connection when done
|
||||
stockResponse, err := inventoryClientRequest.Client.GetStockForProduct(ctx, &inventory.ProductIDRequest{ID: productID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer pricesClientRequest.Conn.Close() // close GRPC connection when done
|
||||
priceResponse, err := pricesClientRequest.Client.GetPricesForProduct(ctx, &prices.ProductIDRequest{ID: productID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.sb.WriteString(fmt.Sprintf("stock %0.2fpcs @ price %0.2f$\n", stockResponse.Stock, priceResponse.Price))
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/badu/bus/test_scenarios/factory-request-reply/inventory"
|
||||
"github.com/badu/bus/test_scenarios/factory-request-reply/prices"
|
||||
)
|
||||
|
||||
type InventoryGRPCClientRequestEvent struct {
|
||||
wg sync.WaitGroup
|
||||
Conn Closer // should be *grpc.ClientConn, but we're avoiding the import
|
||||
Client inventory.ServiceClient
|
||||
}
|
||||
|
||||
func NewInventoryGRPCClientRequestEvent() *InventoryGRPCClientRequestEvent {
|
||||
result := InventoryGRPCClientRequestEvent{}
|
||||
result.wg.Add(1)
|
||||
return &result
|
||||
}
|
||||
|
||||
func (i *InventoryGRPCClientRequestEvent) Async() bool {
|
||||
return true // this one is async
|
||||
}
|
||||
|
||||
func (i *InventoryGRPCClientRequestEvent) WaitReply() {
|
||||
i.wg.Wait()
|
||||
}
|
||||
|
||||
func (i *InventoryGRPCClientRequestEvent) Reply() {
|
||||
i.wg.Done()
|
||||
}
|
||||
|
||||
type PricesGRPCClientRequestEvent struct {
|
||||
wg sync.WaitGroup
|
||||
Conn Closer // should be *grpc.ClientConn, but we're avoiding the import
|
||||
Client prices.ServiceClient
|
||||
}
|
||||
|
||||
func NewPricesGRPCClientRequestEvent() *PricesGRPCClientRequestEvent {
|
||||
result := PricesGRPCClientRequestEvent{}
|
||||
result.wg.Add(1)
|
||||
return &result
|
||||
}
|
||||
|
||||
func (p *PricesGRPCClientRequestEvent) WaitReply() {
|
||||
p.wg.Wait()
|
||||
}
|
||||
|
||||
func (p *PricesGRPCClientRequestEvent) Reply() {
|
||||
p.wg.Done()
|
||||
}
|
||||
|
||||
type Closer interface {
|
||||
Close() error
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package inventory
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type ServiceClient interface {
|
||||
GetStockForProduct(ctx context.Context, in *ProductIDRequest) (*ProductStockResponse, error) // , opts ...grpc.CallOption) (*ProductStockResponse, error)
|
||||
}
|
||||
|
||||
type ProductIDRequest struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
type ProductStockResponse struct {
|
||||
Stock float64
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package inventory
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type ServiceImpl struct {
|
||||
}
|
||||
|
||||
func NewService() ServiceImpl {
|
||||
result := ServiceImpl{}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) GetStockForProduct(ctx context.Context, productID string) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package factory_request_reply
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/badu/bus"
|
||||
"github.com/badu/bus/test_scenarios/factory-request-reply/cart"
|
||||
"github.com/badu/bus/test_scenarios/factory-request-reply/events"
|
||||
"github.com/badu/bus/test_scenarios/factory-request-reply/inventory"
|
||||
"github.com/badu/bus/test_scenarios/factory-request-reply/prices"
|
||||
)
|
||||
|
||||
var sb strings.Builder
|
||||
|
||||
type pricesClientStub struct{}
|
||||
|
||||
func (s *pricesClientStub) GetPricesForProduct(ctx context.Context, in *prices.ProductIDRequest) (*prices.ProductPriceResponse, error) {
|
||||
return &prices.ProductPriceResponse{Price: 10.30}, nil
|
||||
}
|
||||
|
||||
type fakeCloser struct {
|
||||
}
|
||||
|
||||
func (f *fakeCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func OnPricesGRPCClientStubRequest(e *events.PricesGRPCClientRequestEvent) {
|
||||
sb.WriteString("OnPricesGRPCClientStubRequest\n")
|
||||
e.Client = &pricesClientStub{}
|
||||
e.Conn = &fakeCloser{}
|
||||
<-time.After(300 * time.Millisecond)
|
||||
e.Reply()
|
||||
}
|
||||
|
||||
type inventoryClientStub struct{}
|
||||
|
||||
func (s *inventoryClientStub) GetStockForProduct(ctx context.Context, in *inventory.ProductIDRequest) (*inventory.ProductStockResponse, error) {
|
||||
return &inventory.ProductStockResponse{Stock: 200}, nil
|
||||
}
|
||||
|
||||
func OnInventoryGRPCClientStubRequest(e *events.InventoryGRPCClientRequestEvent) {
|
||||
sb.WriteString("OnInventoryGRPCClientStubRequest\n")
|
||||
e.Client = &inventoryClientStub{}
|
||||
e.Conn = &fakeCloser{}
|
||||
<-time.After(300 * time.Millisecond)
|
||||
e.Reply()
|
||||
}
|
||||
|
||||
func TestGRPCClientStub(t *testing.T) {
|
||||
|
||||
cartSvc := cart.NewService(&sb)
|
||||
|
||||
bus.Sub(OnInventoryGRPCClientStubRequest)
|
||||
bus.Sub(OnPricesGRPCClientStubRequest)
|
||||
|
||||
err := cartSvc.AddProductToCart(context.Background(), "1")
|
||||
if err != nil {
|
||||
t.Fatalf("error adding product to cart : %#v", err)
|
||||
}
|
||||
|
||||
err = cartSvc.AddProductToCart(context.Background(), "2")
|
||||
if err != nil {
|
||||
t.Fatalf("error adding product to cart : %#v", err)
|
||||
}
|
||||
|
||||
const expecting = "OnInventoryGRPCClientStubRequest\n" +
|
||||
"OnPricesGRPCClientStubRequest\n" +
|
||||
"stock 200.00pcs @ price 10.30$\n" +
|
||||
"OnInventoryGRPCClientStubRequest\n" +
|
||||
"OnPricesGRPCClientStubRequest\n" +
|
||||
"stock 200.00pcs @ price 10.30$\n"
|
||||
|
||||
got := sb.String()
|
||||
if got != expecting {
|
||||
t.Fatalf("expecting :\n%s but got : \n%s", expecting, got)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package prices
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type ServiceClient interface {
|
||||
GetPricesForProduct(ctx context.Context, in *ProductIDRequest) (*ProductPriceResponse, error) // , opts ...grpc.CallOption) (*ProductPriceResponse, error)
|
||||
}
|
||||
|
||||
type ProductIDRequest struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
type ProductPriceResponse struct {
|
||||
Price float64
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package prices
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type ServiceImpl struct {
|
||||
}
|
||||
|
||||
func NewService() ServiceImpl {
|
||||
result := ServiceImpl{}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) GetPricesForProduct(ctx context.Context, productID string) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package audit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/badu/bus"
|
||||
"github.com/badu/bus/test_scenarios/fire-and-forget/events"
|
||||
)
|
||||
|
||||
type ServiceImpl struct {
|
||||
sb *strings.Builder
|
||||
}
|
||||
|
||||
func NewAuditService(sb *strings.Builder) ServiceImpl {
|
||||
result := ServiceImpl{sb: sb}
|
||||
bus.Sub(result.OnUserRegisteredEvent)
|
||||
bus.SubCancel(result.OnSMSRequestEvent)
|
||||
bus.SubCancel(result.OnSMSSentEvent)
|
||||
return result
|
||||
}
|
||||
|
||||
// OnUserRegisteredEvent is classic event handler
|
||||
func (s *ServiceImpl) OnUserRegisteredEvent(event events.UserRegisteredEvent) {
|
||||
// we can save audit data here
|
||||
}
|
||||
|
||||
// OnSMSRequestEvent is a pub-unsub type, we have to return 'false' to continue listening for this kind of events
|
||||
func (s *ServiceImpl) OnSMSRequestEvent(event events.SMSRequestEvent) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// OnSMSSentEvent is a pub-unsub type where we give up on listening after receiving first message
|
||||
func (s *ServiceImpl) OnSMSSentEvent(event events.SMSSentEvent) bool {
|
||||
s.sb.WriteString(fmt.Sprintf("audit event : an sms was %s sent to %s with message %s\n", event.Status, event.Request.Number, event.Request.Message))
|
||||
return true // after first event, audit will give up listening for events
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package events
|
||||
|
||||
type UserRegisteredEvent struct {
|
||||
UserName string
|
||||
Phone string
|
||||
}
|
||||
|
||||
func (e UserRegisteredEvent) Async() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type SMSRequestEvent struct {
|
||||
Number string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e SMSRequestEvent) Async() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type SMSSentEvent struct {
|
||||
Request SMSRequestEvent
|
||||
Status string
|
||||
}
|
||||
|
||||
func (e SMSSentEvent) Async() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type DummyEvent struct {
|
||||
AlteredAsync bool
|
||||
}
|
||||
|
||||
func (e *DummyEvent) Async() bool {
|
||||
return e.AlteredAsync
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package fire_and_forget
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/badu/bus"
|
||||
"github.com/badu/bus/test_scenarios/fire-and-forget/audit"
|
||||
"github.com/badu/bus/test_scenarios/fire-and-forget/events"
|
||||
"github.com/badu/bus/test_scenarios/fire-and-forget/notifications"
|
||||
"github.com/badu/bus/test_scenarios/fire-and-forget/users"
|
||||
)
|
||||
|
||||
func OnDummyEvent(event *events.DummyEvent) {
|
||||
fmt.Println("dummy event async ?", event.Async())
|
||||
}
|
||||
|
||||
func TestUserRegistration(t *testing.T) {
|
||||
var sb strings.Builder
|
||||
|
||||
userSvc := users.NewService(&sb)
|
||||
notifications.NewSmsService(&sb)
|
||||
notifications.NewEmailService(&sb)
|
||||
audit.NewAuditService(&sb)
|
||||
|
||||
bus.Sub(OnDummyEvent)
|
||||
|
||||
userSvc.RegisterUser(context.Background(), "Badu", "+40742222222")
|
||||
|
||||
<-time.After(500 * time.Millisecond)
|
||||
|
||||
userSvc.RegisterUser(context.Background(), "Adina", "+40743333333")
|
||||
|
||||
<-time.After(500 * time.Millisecond)
|
||||
|
||||
const expecting = "user Badu has registered - sending welcome email message\n" +
|
||||
"sms sent requested for number +40742222222 with message Badu your user account was created. Check your email for instructions\n" +
|
||||
"audit event : an sms was successfully sent sent to +40742222222 with message Badu your user account was created. Check your email for instructions\n" +
|
||||
"user Adina has registered - sending welcome email message\n" +
|
||||
"sms sent requested for number +40743333333 with message Adina your user account was created. Check your email for instructions\n"
|
||||
|
||||
got := sb.String()
|
||||
if got != expecting {
|
||||
t.Fatalf("expecting :\n%s but got : \n%s", expecting, got)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/badu/bus"
|
||||
"github.com/badu/bus/test_scenarios/fire-and-forget/events"
|
||||
)
|
||||
|
||||
type EmailServiceImpl struct {
|
||||
sb *strings.Builder
|
||||
}
|
||||
|
||||
func NewEmailService(sb *strings.Builder) EmailServiceImpl {
|
||||
result := EmailServiceImpl{sb: sb}
|
||||
bus.Sub(result.OnUserRegisteredEvent)
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *EmailServiceImpl) OnUserRegisteredEvent(e events.UserRegisteredEvent) {
|
||||
s.sb.WriteString(fmt.Sprintf("user %s has registered - sending welcome email message\n", e.UserName))
|
||||
bus.Pub(events.SMSRequestEvent{
|
||||
Number: e.Phone,
|
||||
Message: fmt.Sprintf("%s your user account was created. Check your email for instructions", e.UserName),
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/badu/bus"
|
||||
"github.com/badu/bus/test_scenarios/fire-and-forget/events"
|
||||
)
|
||||
|
||||
type SmsServiceImpl struct {
|
||||
sb *strings.Builder
|
||||
}
|
||||
|
||||
func NewSmsService(sb *strings.Builder) SmsServiceImpl {
|
||||
result := SmsServiceImpl{sb: sb}
|
||||
bus.Sub(result.OnSMSSendRequest)
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *SmsServiceImpl) OnSMSSendRequest(event events.SMSRequestEvent) {
|
||||
s.sb.WriteString(fmt.Sprintf("sms sent requested for number %s with message %s\n", event.Number, event.Message))
|
||||
bus.Pub(events.SMSSentEvent{
|
||||
Request: event,
|
||||
Status: "successfully sent",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package users
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/badu/bus"
|
||||
"github.com/badu/bus/test_scenarios/fire-and-forget/events"
|
||||
)
|
||||
|
||||
type ServiceImpl struct {
|
||||
sb *strings.Builder
|
||||
c int
|
||||
}
|
||||
|
||||
func NewService(sb *strings.Builder) ServiceImpl {
|
||||
result := ServiceImpl{sb: sb}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) RegisterUser(ctx context.Context, name, phone string) {
|
||||
s.c++
|
||||
bus.Pub(events.UserRegisteredEvent{UserName: name, Phone: phone})
|
||||
bus.Pub(&events.DummyEvent{AlteredAsync: s.c%2 == 0}) // nobody listens on this one
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package events
|
||||
|
||||
type RequestEvent[T any] struct {
|
||||
Payload T
|
||||
Callback func() (*T, error)
|
||||
Done chan struct{}
|
||||
}
|
||||
|
||||
func NewRequestEvent[T any](payload T) *RequestEvent[T] {
|
||||
return &RequestEvent[T]{
|
||||
Payload: payload,
|
||||
Done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (i *RequestEvent[T]) Async() bool {
|
||||
return true // this one is async
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package request_reply_callback
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/badu/bus/test_scenarios/request-reply-callback/orders"
|
||||
)
|
||||
|
||||
func TestRequestReplyCallback(t *testing.T) {
|
||||
var sb strings.Builder
|
||||
orders.NewRepository(&sb)
|
||||
svc := orders.NewService(&sb)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
newOrder0, err := svc.RegisterOrder(ctx, []int{1, 2, 3})
|
||||
if err != nil {
|
||||
t.Fatalf("error creating order : %#v", err)
|
||||
}
|
||||
|
||||
t.Logf("new order #0 : %#v", newOrder0)
|
||||
|
||||
newOrder1, err := svc.RegisterOrder(ctx, []int{4, 5, 6})
|
||||
if err != nil {
|
||||
t.Fatalf("error creating order : %#v", err)
|
||||
}
|
||||
|
||||
t.Logf("new order #1 : %#v", newOrder1)
|
||||
newOrder2, err := svc.RegisterOrder(ctx, []int{7, 8, 9})
|
||||
if err != nil {
|
||||
t.Fatalf("error creating order : %#v", err)
|
||||
}
|
||||
|
||||
t.Logf("new order #2 : %#v", newOrder2)
|
||||
|
||||
stat0, err := svc.GetOrderStatus(ctx, newOrder0.OrderID)
|
||||
if err != nil {
|
||||
t.Fatalf("error getting order status : %#v", err)
|
||||
}
|
||||
t.Logf("order #0 status : %s", stat0.Status)
|
||||
|
||||
stat1, err := svc.GetOrderStatus(ctx, newOrder1.OrderID)
|
||||
if err != nil {
|
||||
t.Fatalf("error getting order status : %#v", err)
|
||||
}
|
||||
|
||||
t.Logf("order #1 status : %s", stat1.Status)
|
||||
|
||||
stat2, err := svc.GetOrderStatus(ctx, newOrder2.OrderID)
|
||||
if err != nil {
|
||||
t.Fatalf("error getting order status : %#v", err)
|
||||
}
|
||||
t.Logf("order #2 status : %s", stat2.Status)
|
||||
|
||||
t.Logf("%s", sb.String())
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package orders
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/badu/bus"
|
||||
"github.com/badu/bus/test_scenarios/request-reply-callback/events"
|
||||
)
|
||||
|
||||
type Order struct {
|
||||
OrderID int
|
||||
ProductIDs []int
|
||||
}
|
||||
|
||||
type OrderStatus struct {
|
||||
OrderID int
|
||||
Status string
|
||||
}
|
||||
|
||||
type RepositoryImpl struct {
|
||||
sb *strings.Builder
|
||||
calls int
|
||||
}
|
||||
|
||||
func NewRepository(sb *strings.Builder) RepositoryImpl {
|
||||
result := RepositoryImpl{sb: sb}
|
||||
bus.Sub(result.onCreateOrder)
|
||||
bus.Sub(result.onGetOrderStatus)
|
||||
return result
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) onCreateOrder(event *events.RequestEvent[Order]) {
|
||||
defer func() { r.calls++ }()
|
||||
|
||||
<-time.After(500 * time.Millisecond) // simulate heavy database call
|
||||
|
||||
event.Callback = func() (*Order, error) {
|
||||
return &Order{OrderID: r.calls, ProductIDs: event.Payload.ProductIDs}, nil
|
||||
}
|
||||
|
||||
close(event.Done)
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) onGetOrderStatus(event *events.RequestEvent[OrderStatus]) {
|
||||
<-time.After(300 * time.Millisecond) // simulate heavy database call
|
||||
|
||||
event.Callback = func() (*OrderStatus, error) {
|
||||
status := "in_progress"
|
||||
if event.Payload.OrderID == 3 {
|
||||
status = "cancelled"
|
||||
}
|
||||
return &OrderStatus{OrderID: event.Payload.OrderID, Status: status}, nil
|
||||
}
|
||||
|
||||
close(event.Done)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package orders
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/badu/bus"
|
||||
"github.com/badu/bus/test_scenarios/request-reply-callback/events"
|
||||
)
|
||||
|
||||
type ServiceImpl struct {
|
||||
sb *strings.Builder
|
||||
}
|
||||
|
||||
func NewService(sb *strings.Builder) ServiceImpl {
|
||||
result := ServiceImpl{sb: sb}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) RegisterOrder(ctx context.Context, productIDs []int) (*Order, error) {
|
||||
event := events.NewRequestEvent[Order](Order{ProductIDs: productIDs})
|
||||
s.sb.WriteString(fmt.Sprintf("dispatching event typed %T\n", event))
|
||||
bus.Pub(event)
|
||||
<-event.Done // wait for "reply"
|
||||
return event.Callback() // return the callback, which is containing the actual result
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) GetOrderStatus(ctx context.Context, orderID int) (*OrderStatus, error) {
|
||||
event := events.NewRequestEvent[OrderStatus](OrderStatus{OrderID: orderID})
|
||||
s.sb.WriteString(fmt.Sprintf("dispatching event typed %T\n", event))
|
||||
bus.Pub(event)
|
||||
<-event.Done // wait for "reply"
|
||||
return event.Callback() // return the callback, which is containing the actual result
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type EventState struct {
|
||||
Ctx context.Context
|
||||
Done chan struct{} `json:"-"`
|
||||
Error error
|
||||
}
|
||||
|
||||
func NewEventState(ctx context.Context) *EventState {
|
||||
return &EventState{
|
||||
Ctx: ctx,
|
||||
Done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *EventState) Close() {
|
||||
s.Error = s.Ctx.Err()
|
||||
close(s.Done)
|
||||
}
|
||||
|
||||
type NewOrder struct {
|
||||
ID int
|
||||
}
|
||||
|
||||
type CreateOrderEvent struct {
|
||||
NewOrder *NewOrder
|
||||
ProductIDs []int
|
||||
State *EventState
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package request_reply
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/badu/bus/test_scenarios/request-reply-with-cancellation/orders"
|
||||
)
|
||||
|
||||
func TestRequestReplyWithCancellation(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*1)
|
||||
svc := orders.NewService()
|
||||
orders.NewRepository()
|
||||
|
||||
response, err := svc.CreateOrder(ctx, []int{1, 2, 3})
|
||||
switch err {
|
||||
default:
|
||||
t.Fatalf("error : it supposed to timeout, but it responded %#v and the error is %#v", response, err)
|
||||
case context.DeadlineExceeded:
|
||||
// what we were expecting
|
||||
}
|
||||
|
||||
cancel()
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package orders
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/badu/bus"
|
||||
"github.com/badu/bus/test_scenarios/request-reply-with-cancellation/events"
|
||||
)
|
||||
|
||||
type Order struct {
|
||||
ID int
|
||||
ProductIDs []int
|
||||
}
|
||||
|
||||
type RepositoryImpl struct {
|
||||
calls int
|
||||
}
|
||||
|
||||
func NewRepository() RepositoryImpl {
|
||||
result := RepositoryImpl{}
|
||||
bus.Sub(result.OnCreateOrder)
|
||||
return result
|
||||
}
|
||||
|
||||
func (r *RepositoryImpl) OnCreateOrder(event events.CreateOrderEvent) {
|
||||
defer func() {
|
||||
r.calls++
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-time.After(4 * time.Second):
|
||||
event.NewOrder = &events.NewOrder{ID: r.calls}
|
||||
event.State.Close()
|
||||
return
|
||||
case <-event.State.Ctx.Done():
|
||||
event.State.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package orders
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/badu/bus"
|
||||
"github.com/badu/bus/test_scenarios/request-reply-with-cancellation/events"
|
||||
)
|
||||
|
||||
type ServiceImpl struct {
|
||||
}
|
||||
|
||||
func NewService() ServiceImpl {
|
||||
result := ServiceImpl{}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *ServiceImpl) CreateOrder(ctx context.Context, productIDs []int) (*Order, error) {
|
||||
event := events.CreateOrderEvent{State: events.NewEventState(ctx), ProductIDs: productIDs, NewOrder: &events.NewOrder{}}
|
||||
bus.Pub(event)
|
||||
<-event.State.Done
|
||||
|
||||
if event.NewOrder != nil && event.State.Error == nil {
|
||||
return &Order{ID: event.NewOrder.ID, ProductIDs: productIDs}, nil
|
||||
}
|
||||
|
||||
return nil, event.State.Error
|
||||
}
|
||||
Reference in New Issue
Block a user