@@ -26,6 +26,8 @@ The event producer will simply do:
2626
2727` bus.Pub(InterestingEvent{}) `
2828
29+ Usage : ` go get github.com/badu/bus `
30+
2931## What Problem Does It Solve?
3032
3133Decoupling of components: publishers and subscribers can operate independently of each other, with no direct knowledge
@@ -47,18 +49,77 @@ Each component can then be developed and tested independently, making the overal
4749
4850Inside the ` test_scenarios ` folder, you can find the following scenarios:
4951
50- 1 . Fire and Forget
52+ 1 . Fire and Forget.
53+
54+ Imagine a system / application where we have three services : ` users ` , ` notifications ` (email and
55+ SMS) and ` audit ` . When a user registers, we want to send welcoming messages via SMS and email, but we also want to
56+ audit that registration for reporting purposes.
57+
58+ The [ UserRegisteredEvent] ( https://github.com/badu/bus/blob/main/test_scenarios/fire-and-forget/events/main.go#L10 )
59+ will carry the freshly registered username (which is also the email) and phone to the email and sms services. The
60+ event is [ triggered] ( https://github.com/badu/bus/blob/main/test_scenarios/fire-and-forget/users/service.go#L21 ) by
61+ the user service, which performs the creation of the user account. We're using the ` fire and forget ` technique here,
62+ because the operation of registration should not depend on the fact that we've been able to
63+ send an welcoming email or a sms, or the audit system malfunctions.
64+
65+ Simulating audit service malfunction is easy. Instead of using ` Sub ` , we're using ` SubUnsub ` to register the listener
66+ and return [ ` true ` ] ( https://github.com/badu/bus/blob/main/test_scenarios/fire-and-forget/audit/service.go#L36 ) to
67+ unsubscribe on events of that kind.
68+
69+ 2 . Factory Request Reply
70+
71+ Imagine a system / application where we need to communicate with different microservices, but in this case we don't
72+ want to bring them online, we're just wanting to stub the response as those services were alive.
73+
74+ This technique is useful when we need to test some complicated flows of business logic and facilitates the
75+ transformation of an integration test into a classic unit test.
76+
77+ The ` cart ` service requires two replies from two other microservices ` inventory ` and ` prices ` . In the past, I've been
78+ using a closure function to provide the service with both real GRPC clients or with mocks and stubs. The service
79+ signature gets complicated and large as we one service would depend on a lot of GRPC clients to aggregate data.
80+
81+ As you can see
82+ the [ test here] ( https://github.com/badu/bus/blob/main/test_scenarios/factory-request-reply/main_test.go ) it's much
83+ more elegant and the service constructor is much slimmer.
84+
85+ Events are one sync and one async, just to check it works in both scenarios.
86+
87+ Important to note that because a ` WaitGroup ` is being used in our event struct, we're forced to pass the events by
88+ using a pointer, instead of passing them by value.
89+
90+ 3 . Request Reply with Callback
91+
92+ In this example, we wanted to achieve two things. First is that the ` service ` and the ` repository ` are decoupled by
93+ events. More than that, we wanted that the events are generic on their own.
94+
95+ The orders service will dispatch a generic request event, one for placing an order, which will carry an ` Order ` (
96+ model) struct with that request and another ` OrderStatus ` (model) struct using the same generic event.
97+
98+ We are using a channel inside the generic ` RequestEvent ` to signal the ` reply ` to the publisher, which in this case
99+ is a callback function that returns the result as if the publisher would have called directly the listener.
100+
101+ I am sure that you will find this technique interesting and having a large number of applications.
102+
103+ An important note is about not forgetting to implement the ` EventID() string ` correctly, as incorrect naming triggers
104+ panic (expecting one type of event, but receiving another). To exemplify this, just alter the return of
105+ this [ function] ( https://github.com/badu/bus/blob/main/test_scenarios/request-reply-callback/events/main.go#L24 ) .
51106
52- 2 . Request Reply with Callback
107+ 4 . Request Reply with Cancellation
53108
54- 3 . Request Reply with Cancellation
109+ Last but, not least, this is an example about providing ` context.Context ` along the publisher subscriber chain.
110+ The ` repository ` is simulating a long database call, longer than the context's cancellation, so the service gets the
111+ deadline exceeded error.
55112
56- 4 . Factory Request Reply
113+ Note that this final example is not using pointer to the event's struct, but it contains two properties which have
114+ pointers, so the ` service ` can access the altered ` reply ` .
57115
58116## Recommendations
59117
60- 1 . when using ` sync.WaitGroup ` inside your event struct, always use method receivers and pass the event as pointer,
61- otherwise you will be passing a lock by value (which is ` sync.Locker ` ).
118+ 1 . always place your events inside a separate ` events ` package, avoiding circular dependencies.
119+ 2 . in general, in ` request-reply ` scenarios, the events should be passed as pointers (even if it's somewhat slower),
120+ because changing properties that represents the ` reply ` would not be reflected. Also, when using ` sync.WaitGroup `
121+ inside your event struct, always use method receivers and pass the event as pointer, otherwise you will be passing a
122+ lock by value (which is ` sync.Locker ` ).
621232 . be careful if you don't want to use pointers for events, but you still need to pass values from the listener to the
63124 dispatcher. You should still have at least one property of that event that is a pointer (see events
64125 in ` request reply with cancellation ` for example). Same technique can be applied when you need ` sync.Waitgroup ` to be
0 commit comments