├── README.md ├── consumer └── consumer.go ├── lib └── event │ ├── consumer.go │ ├── emitter.go │ ├── event.go │ └── event_test.go └── sender └── sender.go /README.md: -------------------------------------------------------------------------------- 1 | # Using RabbitMQ with Golang 2 | 3 | ## Golang 4 | So, why Golang? Why not Java or Python? Java has a much larger user base and Python is a much simpler language to write. So why choose Golang? Well. Golang is a modern language, which has great support for modern software architecture. Golang is a very small language, it's compiled (not transpiled and not run by the JVM) so, building Docker containers with Golang is a match made in heaven. Small Docker images, with performance similar to languages like C. So, this also makes Golang a great language for writing microservices... And, you can't say microservices without saying event-driven architecture! So, let's write a simple Golang program, to exemplify using Golang with RabbitMQ to support and event driven architecture. 5 | 6 | There are many other fantastic features of Golang, but I won't go too much into detail. If you are interested, I would recommend watching this short interview with Nic Jackson from Hashicorp: https://www.youtube.com/watch?v=qlwp0mHFLHU 7 | 8 | ## Event-Driven Architecture 9 | Event Driven Architecture has been popular looooooooooong before Microservices, but now that Microservices are all the talk, so is EDA. Essentially, EDA is a pattern for communication of state. It's been immensely popular in the financial industry for decades, as the pattern is particularly suited for handling transaction state. The reason why it has become so attached to the conversation of Microservices, is that in Microservice Architecture, you want everything to be loosely coupled. Essentially, you don't want one service to be attached to another. You want to avoid situations in which you change something in one service and then must make a corresponding change to one or all other services. 10 | 11 | Let's think of an HTTP service, in which we are communicating with one or more services. Who decides who receives data? It's the HTTP service, which directly calls each and every one of those services. So... what happens if we create a new service that also needs this data? We would have to ask whoever is maintaining the HTTP service, if they could make sure, that our service also could receive this data. 12 | 13 | However, in an EDA, we don't need to contact the HTTP service owners at all. An EDA typically works in a publish/subscribe pattern. Simply explained, a publisher sends a message to a message broker, who will appropriately deliver the messages to all services who are subscribed. So, if we need to create a new service, we simply tell the message broker that we are subscribing to these messages/events. The HTTP service guys don't need to know about us, and we don't need to talk to them (on a non-technical level, socially, this is also typically regarded as a win). 14 | 15 | Now, there are many other advantages of EDA. But I will leave that to others to explain. 16 | 17 | 18 | ## Asynchronous Messaging Queue Protocol 19 | The Asynchronous Messaging Queue Protocol started development in 2003, initiated by JPMorgan Chase. The project soon caught on and became a open-source project involving some of the largest banks and technology companies (Bank of America, Barclays, Microsoft, Cisco etc.) Essentially, the project was meant to create an open standard, to improve transactions, with a focus on the financial industry. Therefore, there was a huge backing by the banking industry to develop AMQP, making it extremely efficient and reliable. AMQP relies on messaging queues to handle communication, in a so called publish/subscribe architecture. The most common pattern of implementing this, the pattern this tutorial will be looking at, is the `topic exchange`. Essentially, a publisher sends a message to an `exchange` which will distribute messages to queues, based on a `topic`. The subscriber(s) will define a `queue` and tell the exchange which `topics` they are interested in. 20 | 21 | As an example: If we, as a subscriber define a queue in which we define to be interested in all messages with the topic `apple`, if a publisher sends a message with `apple` we will receive that message. Even further, we can define that we are interested in sub topics, which is a typical implementation for logging. So, as an example, I might have a subscriber who is listening for `log.ERROR` and `log.CRITICAL`, but have another subscriber who is interested in all log `log.*`. In other words, it's possible to listen based on binding keys (which work like search filters). This is super neat and something that we will explore further in this tutorial, using RabbitMQ, which implements AMQP. If you wish to use something other than RabbitMQ, then your in luck, because any asynchronous message system that supports AMQP will work with the code in this tutorial. 22 | 23 | So, AMQP seems rather simple, right? It is, and that is why it's so great. We define a `publisher` who sends a message with a specified `topic` to an `exchange`. The `exchange` will determine whom to send these message to, based on `subscribers` `topic` binding keys. 24 | 25 | ## Prerequisites 26 | ### Text Editor 27 | It doesn't matter what you use, use what you feel comfortable in. Personally, I use visual code. It's free and super easy to setup. For installation instructions, go to: https://code.visualstudio.com/ 28 | 29 | ### Docker 30 | I will be using RabbitMQ, by spinning up a Docker image locally on my machine. You don't need docker to run RabbitMQ, but I would recommend using a local Docker instance, at least for this short tutorial. Docker installation instructions can be found here: https://docs.docker.com/install/ 31 | 32 | ### Golang 33 | Installation of Golang is nice and easy. Instructions and binaries can be found at the official Golang site: https://golang.org/doc/install 34 | 35 | For this tutorial, I assume some basic understanding of programming and also some very basics of Golang. I will try to explain everything as well as possible, but of course, prior experience with Golang is an advantage. 36 | 37 | ## Spinning up RabbitMQ 38 | With Docker, this is super simple. Simply type the following command in your terminal: 39 | 40 | > docker run --detach --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management 41 | 42 | We are running a docker image, running the container in --detach mode (as a background process) naming it rabbitmq and exposing ports 5672 and 15672. Finally, we specify the image that we want to pull and eventually run: `rabbitmq:3-management`. Once the docker container has started, you can open a browser and visit http://localhost:15672 to see the management interface. We won't be using the mangement interface, but it's a good way to confirm that everything is working as intended. 43 | 44 | ## Writing the Code 45 | If you want to skip writing the code, but instead just want to read through and run the programs yourself. You can get the code from: https://github.com/Pungyeon/go-rabbitmq-example 46 | 47 | So for this tutorial, we will be writing two really simple programs, to illustrate how services can communicate via. RabbitMQ. Our final project will look something like this: 48 | go-rabbit-mq/ 49 | 50 | ----./consumer 51 | 52 | ----./lib 53 | 54 | --------./event 55 | 56 | ----./sender 57 | 58 | We will be creating a `consumer` service, which will subscribe to our topics and we will define a `sender`service, which will publish random events to the exchange. Our `lib` folder, will hold some common configurations for both our consumer and sender. Before we begin, you will have to get the dependency for amqp: 59 | 60 | `go get github.com/streadway/amqp` 61 | 62 | But that's it, now we are ready to write some code. 63 | 64 | ### Event Queue 65 | All files in this section will be placed in `lib/event`. 66 | 67 | #### ./lib/event/event.go 68 | First, we will write our library consisting of queue declaration and our structs for consumer and emitter. We will however start with some simple queue and exchange declaration: 69 | ```go 70 | package event 71 | 72 | import ( 73 | "github.com/streadway/amqp" 74 | ) 75 | 76 | func getExchangeName() string { 77 | return "logs_topic" 78 | } 79 | 80 | func declareRandomQueue(ch *amqp.Channel) (amqp.Queue, error) { 81 | return ch.QueueDeclare( 82 | "", // name 83 | false, // durable 84 | false, // delete when unused 85 | true, // exclusive 86 | false, // no-wait 87 | nil, // arguments 88 | ) 89 | } 90 | 91 | func declareExchange(ch *amqp.Channel) error { 92 | return ch.ExchangeDeclare( 93 | getExchangeName(), // name 94 | "topic", // type 95 | true, // durable 96 | false, // auto-deleted 97 | false, // internal 98 | false, // no-wait 99 | nil, // arguments 100 | ) 101 | } 102 | ``` 103 | 104 | In this file, we are defining three static methods. The `getExchangeName` function simply returns the name of our exchange. It isn't necessary, but nice for this tutorial, to make it simple to change your topic name. More interesting is the `declareRandomQueue` function. This function will create a nameless queue, which RabbitMQ will assign a random name, we don't want to worry about this and that is why we are letting RabbitMQ worry about it. The queue is also defined as `exclusive`, which means that when defined only one subscriber can be subscribed to this queue. The last function that we have declared is `declareExchange` which will declare an exchange, as the name suggests. This function is idempotent, so if the exchange already exists, no worries, it won't create duplicates. However, if we were to change the type of the Exchange (to direct or fanout), then we would have to either delete the old exchange or find a new name, as you cannot overwrite exchanges. The topic type is what enables us to publish an event with a topic such as `log.WARN`, which the subscribers can specify in their binding keys. 105 | 106 | *NOTE: You might have noticed that both functions need an amqp.Channel struct. This is simply a pointer to an AMQP connection channel. We will explain this a little better later* 107 | 108 | ### ./lib/event/emitter.go 109 | Next, we will define our publisher. I have chosen to call it emitter, because I wanted to add extra confusion... Either way... This is our publisher. Which will publish, or in our case emit, events. 110 | 111 | ```go 112 | package event 113 | 114 | import ( 115 | "log" 116 | 117 | "github.com/streadway/amqp" 118 | ) 119 | 120 | // Emitter for publishing AMQP events 121 | type Emitter struct { 122 | connection *amqp.Connection 123 | } 124 | 125 | func (e *Emitter) setup() error { 126 | channel, err := e.connection.Channel() 127 | if err != nil { 128 | panic(err) 129 | } 130 | 131 | defer channel.Close() 132 | return declareExchange(channel) 133 | } 134 | 135 | // Push (Publish) a specified message to the AMQP exchange 136 | func (e *Emitter) Push(event string, severity string) error { 137 | channel, err := e.connection.Channel() 138 | if err != nil { 139 | return err 140 | } 141 | 142 | defer channel.Close() 143 | 144 | err = channel.Publish( 145 | getExchangeName(), 146 | severity, 147 | false, 148 | false, 149 | amqp.Publishing{ 150 | ContentType: "text/plain", 151 | Body: []byte(event), 152 | }, 153 | ) 154 | log.Printf("Sending message: %s -> %s", event, getExchangeName()) 155 | return nil 156 | } 157 | 158 | // NewEventEmitter returns a new event.Emitter object 159 | // ensuring that the object is initialised, without error 160 | func NewEventEmitter(conn *amqp.Connection) (Emitter, error) { 161 | emitter := Emitter{ 162 | connection: conn, 163 | } 164 | 165 | err := emitter.setup() 166 | if err != nil { 167 | return Emitter{}, err 168 | } 169 | 170 | return emitter, nil 171 | } 172 | ``` 173 | 174 | At the very top of our code, we are defining our Emitter struct (a class), which contains an amqp.Connection. 175 | 176 | **setup** - Makes sure that the exchange that we are sending messages to actually exists. We do this by retreiving a channel from our connection pool and calling the idempotent declareExchange function from our event.go file. 177 | 178 | **Push** - Sends a message to our exchange. First we get a new `channel` from our connection pool and if we receive no errors when doing so, we publish our message. The function takes two input parameters `event` and `severity`; `event` is the message to be sent and severity is our logging serverity, which will define which messages are received by which subscribers, based on their binding keys. 179 | 180 | **NewEventEmitter** - Will return a new Emitter, or an error, making sure that the connection is established to our AMQP server. 181 | 182 | The last bit of code to write for our library, is our consumer struct and right away we can see that it is somewhat similar to our emitter struct. 183 | #### ./lib/event/consumer.go 184 | ```go 185 | package event 186 | 187 | import ( 188 | "log" 189 | 190 | "github.com/streadway/amqp" 191 | ) 192 | 193 | // Consumer for receiving AMPQ events 194 | type Consumer struct { 195 | conn *amqp.Connection 196 | queueName string 197 | } 198 | 199 | func (consumer *Consumer) setup() error { 200 | channel, err := consumer.conn.Channel() 201 | if err != nil { 202 | return err 203 | } 204 | return declareExchange(channel) 205 | } 206 | 207 | // NewConsumer returns a new Consumer 208 | func NewConsumer(conn *amqp.Connection) (Consumer, error) { 209 | consumer := Consumer{ 210 | conn: conn, 211 | } 212 | err := consumer.setup() 213 | if err != nil { 214 | return Consumer{}, err 215 | } 216 | 217 | return consumer, nil 218 | } 219 | 220 | // Listen will listen for all new Queue publications 221 | // and print them to the console. 222 | func (consumer *Consumer) Listen(topics []string) error { 223 | ch, err := consumer.conn.Channel() 224 | if err != nil { 225 | return err 226 | } 227 | defer ch.Close() 228 | 229 | q, err := declareRandomQueue(ch) 230 | if err != nil { 231 | return err 232 | } 233 | 234 | for _, s := range topics { 235 | err = ch.QueueBind( 236 | q.Name, 237 | s, 238 | getExchangeName(), 239 | false, 240 | nil, 241 | ) 242 | 243 | if err != nil { 244 | return err 245 | } 246 | } 247 | 248 | msgs, err := ch.Consume(q.Name, "", true, false, false, false, nil) 249 | if err != nil { 250 | return err 251 | } 252 | 253 | forever := make(chan bool) 254 | go func() { 255 | for d := range msgs { 256 | log.Printf("Received a message: %s", d.Body) 257 | } 258 | }() 259 | 260 | log.Printf("[*] Waiting for message [Exchange, Queue][%s, %s]. To exit press CTRL+C", getExchangeName(), q.Name) 261 | <-forever 262 | return nil 263 | } 264 | ``` 265 | 266 | At the very top we define that our `Consumer` struct defines a connection to our AMQP server and a queueName. The queue name will store the randomly generated name of our declared nameless queue. We will use this for telling RabbitMQ that we want to bind/listen to this particular queue for messages. 267 | 268 | **setup()** - We ensure that the exchange is declared, just like we do in our Emitter struct. 269 | 270 | **NewConsumer()** - We return a new Consumer or an error, ensuring that everything went well connecting to our AMQP server. 271 | 272 | **Listen** - We get a new channel from our connection pool. We declare our nameless queue and then we iterate over our input `topics`, which is just an array of strings, specifying our binding keys. For each string in topics, we will bind our queue to the exchange, specifying our binding key, for which messages we want to receive. As an example, this could be `log.WARN` and `log.ERROR`. Lastly, we will invoke the Consume function (to start listening on the queue) and define that we will iterate over all messages received from the queue and print out these message to the console. 273 | 274 | The `forever` channel that we are making on line #69, and sending output from on line #77, is just a dummy. This is a simple way of ensuring a program will run forever. Essentially, we are defining a channel, which we will wait for until it receives input, but we will never actually send it any input. It's a bit dirty, but for this tutorial it will suffice. 275 | 276 | ### Consumer Service 277 | All files in this section will be placed in the `consumer` folder. 278 | 279 | #### ./consumer/consumer.go 280 | ```go 281 | package main 282 | 283 | import ( 284 | "os" 285 | 286 | "github.com/Pungyeon/go-rabbitmq-example/lib/event" 287 | "github.com/streadway/amqp" 288 | ) 289 | 290 | func main() { 291 | connection, err := amqp.Dial("amqp://guest:guest@localhost:5672") 292 | if err != nil { 293 | panic(err) 294 | } 295 | defer connection.Close() 296 | 297 | consumer, err := event.NewConsumer(connection) 298 | if err != nil { 299 | panic(err) 300 | } 301 | consumer.Listen(os.Args[1:]) 302 | } 303 | ``` 304 | 305 | As can be seen this is a really simple program which creates a connection to our docker instance of RabbitMQ, passes this connection to our `NewConsumer` function and then calls the `Listen` method, passing all the input arguments from the command line. Once we have written this code we can open up a few terminals to start up a few consumers: 306 | 307 | >#t1> go run consumer.go log.WARN log.ERROR 308 | 309 | >#t2> go run consumer.go log.* 310 | 311 | The first terminal in which we are running our consumer.go file, we are listening for all log.WARN and log.ERROR events. In the second terminal we are listening for all events. It is also possible to do a lot of other search filters with binding keys. There are only two different kind of binding keys `*` and `#`: 312 | 313 | `*` substitutes exactly one word. So our binding key could be: `apples.*.orange`and we would receive `apples.dingdong.orange`. Similarly, we would receive `log.WARN` if our binding was `log.*`, but we wouldn't receive `log.WARN.joke` 314 | `#`: substitutes zero or more words. So if we use the same example as above: If our binding is `log.#` we will receive `log.WARN.joke` as well as receiving `log.WARN`. 315 | 316 | There isn't really more to it than that, which honestly, is quite nice :) 317 | 318 | ### Emitter Service 319 | Now for the very last bit of this tutorial. Publishing our messages to the queue: 320 | 321 | ```go 322 | package main 323 | 324 | import ( 325 | "fmt" 326 | "os" 327 | 328 | "github.com/Pungyeon/go-rabbitmq-example/lib/event" 329 | "github.com/streadway/amqp" 330 | ) 331 | 332 | func main() { 333 | conn, err := amqp.Dial("amqp://guest:guest@localhost:5672") 334 | if err != nil { 335 | panic(err) 336 | } 337 | 338 | emitter, err := event.NewEventEmitter(conn) 339 | if err != nil { 340 | panic(err) 341 | } 342 | 343 | for i := 1; i < 10; i++ { 344 | emitter.Push(fmt.Sprintf("[%d] - %s", i, os.Args[1]), os.Args[1]) 345 | } 346 | } 347 | ``` 348 | Again, a very simply little service. Connection to AMQP, create a new Event Emitter and then iterate to publish 10 messages to the exchange, using the console input as severity level. The `Push` function being input (message: "i - input", severity: input). Simples. So, run this a few times and see what happens: 349 | 350 | > #t3> go run sender.go log.WARN 351 | 352 | > #t3> go run sender.go log.ERROR 353 | 354 | > #t3> go run sender.go log.INFO 355 | 356 | Wow! As expected our two other services are now receiving messages independantly of each other, only receiving the messages that they are subscribed to. 357 | 358 | ## Final remarks 359 | So, of course, this is a super simple implementation of how AMQP works. There are so many other, more exciting, functionalities that can be implemented with AMQP and Event Driven Architecture. I suggest to try to implement a simple API service, that uses RabbitMQ for event auditing. Sending all events of the API to the messaging broker and saving them as auditing logs. This can then be extended to Event Sourcing, by using this log to regenerate state in your application, by going through the auditing logs and then based on those logs, recreating the data in your applications. This is somewhat complicated and there are a whole lot of considerations to be made.... but it's also really fun to experiment with :) 360 | 361 | If anything, I strongly suggest looking at implementing a messaging broker where it makes sense. As an example: Microservices that needs loosely coupled communication or distributed services where we need to replicate state across services. More than anything, I suggest having a look at Martin Fowler's excellent talk on Event-Driven Architecture from 2017. Martin Fowler is a bit of a guru on software design and architecture and this talk certainly doesn't disappoint: https://www.youtube.com/watch?v=STKCRSUsyP0 and if you don't like videos, he has also written a little about it here: https://martinfowler.com/articles/201701-event-driven.html 362 | -------------------------------------------------------------------------------- /consumer/consumer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/Pungyeon/go-rabbitmq-example/lib/event" 7 | "github.com/streadway/amqp" 8 | ) 9 | 10 | func main() { 11 | connection, err := amqp.Dial("amqp://guest:guest@localhost:5672") 12 | if err != nil { 13 | panic(err) 14 | } 15 | defer connection.Close() 16 | 17 | consumer, err := event.NewConsumer(connection) 18 | if err != nil { 19 | panic(err) 20 | } 21 | consumer.Listen(os.Args[1:]) 22 | } 23 | -------------------------------------------------------------------------------- /lib/event/consumer.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/streadway/amqp" 7 | ) 8 | 9 | // Consumer for receiving AMPQ events 10 | type Consumer struct { 11 | conn *amqp.Connection 12 | queueName string 13 | } 14 | 15 | func (consumer *Consumer) setup() error { 16 | channel, err := consumer.conn.Channel() 17 | if err != nil { 18 | return err 19 | } 20 | return declareExchange(channel) 21 | } 22 | 23 | // NewConsumer returns a new Consumer 24 | func NewConsumer(conn *amqp.Connection) (Consumer, error) { 25 | consumer := Consumer{ 26 | conn: conn, 27 | } 28 | err := consumer.setup() 29 | if err != nil { 30 | return Consumer{}, err 31 | } 32 | 33 | return consumer, nil 34 | } 35 | 36 | // Listen will listen for all new Queue publications 37 | // and print them to the console. 38 | func (consumer *Consumer) Listen(topics []string) error { 39 | ch, err := consumer.conn.Channel() 40 | if err != nil { 41 | return err 42 | } 43 | defer ch.Close() 44 | 45 | q, err := declareRandomQueue(ch) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | for _, s := range topics { 51 | err = ch.QueueBind( 52 | q.Name, 53 | s, 54 | getExchangeName(), 55 | false, 56 | nil, 57 | ) 58 | 59 | if err != nil { 60 | return err 61 | } 62 | } 63 | 64 | msgs, err := ch.Consume(q.Name, "", true, false, false, false, nil) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | forever := make(chan bool) 70 | go func() { 71 | for d := range msgs { 72 | log.Printf("Received a message: %s", d.Body) 73 | } 74 | }() 75 | 76 | log.Printf("[*] Waiting for message [Exchange, Queue][%s, %s]. To exit press CTRL+C", getExchangeName(), q.Name) 77 | <-forever 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /lib/event/emitter.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/streadway/amqp" 7 | ) 8 | 9 | // Emitter for publishing AMQP events 10 | type Emitter struct { 11 | connection *amqp.Connection 12 | } 13 | 14 | func (e *Emitter) setup() error { 15 | channel, err := e.connection.Channel() 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | defer channel.Close() 21 | return declareExchange(channel) 22 | } 23 | 24 | // Push (Publish) a specified message to the AMQP exchange 25 | func (e *Emitter) Push(event string, severity string) error { 26 | channel, err := e.connection.Channel() 27 | if err != nil { 28 | return err 29 | } 30 | 31 | defer channel.Close() 32 | 33 | err = channel.Publish( 34 | getExchangeName(), 35 | severity, 36 | false, 37 | false, 38 | amqp.Publishing{ 39 | ContentType: "text/plain", 40 | Body: []byte(event), 41 | }, 42 | ) 43 | log.Printf("Sending message: %s -> %s", event, getExchangeName()) 44 | return nil 45 | } 46 | 47 | // NewEventEmitter returns a new event.Emitter object 48 | // ensuring that the object is initialised, without error 49 | func NewEventEmitter(conn *amqp.Connection) (Emitter, error) { 50 | emitter := Emitter{ 51 | connection: conn, 52 | } 53 | 54 | err := emitter.setup() 55 | if err != nil { 56 | return Emitter{}, err 57 | } 58 | 59 | return emitter, nil 60 | } 61 | -------------------------------------------------------------------------------- /lib/event/event.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "github.com/streadway/amqp" 5 | ) 6 | 7 | func getExchangeName() string { 8 | return "logs_topic" 9 | } 10 | 11 | func declareRandomQueue(ch *amqp.Channel) (amqp.Queue, error) { 12 | return ch.QueueDeclare( 13 | "", // name 14 | false, // durable 15 | false, // delete when unused 16 | true, // exclusive 17 | false, // no-wait 18 | nil, // arguments 19 | ) 20 | } 21 | 22 | func declareExchange(ch *amqp.Channel) error { 23 | return ch.ExchangeDeclare( 24 | getExchangeName(), // name 25 | "topic", // type 26 | true, // durable 27 | false, // auto-deleted 28 | false, // internal 29 | false, // no-wait 30 | nil, // arguments 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /lib/event/event_test.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/streadway/amqp" 7 | ) 8 | 9 | func TestEmitterCreateSuccess(t *testing.T) { 10 | conn, err := amqp.Dial("amqp://guest:guest@localhost:5672") 11 | if err != nil { 12 | t.Errorf("Could not establish a connection to AMQP server: %v", err) 13 | } 14 | defer conn.Close() 15 | _, err = NewEventEmitter(conn) 16 | if err != nil { 17 | t.Errorf("Error creating Event Emitter: %v", err) 18 | } 19 | } 20 | 21 | func TestEmitterPushSuccess(t *testing.T) { 22 | conn, err := amqp.Dial("amqp://guest:guest@localhost:5672") 23 | if err != nil { 24 | t.Errorf("Could not establish a connection to AMQP server: %v", err) 25 | } 26 | 27 | defer conn.Close() 28 | emitter, err := NewEventEmitter(conn) 29 | if err != nil { 30 | t.Errorf("Error creating Event Emitter: %v", err) 31 | } 32 | 33 | err = emitter.Push("Hello World!", "INFO") 34 | if err != nil { 35 | t.Errorf("Could not push to queue successfully: %v", err) 36 | } 37 | } 38 | 39 | func TestConsumerCreatSuccess(t *testing.T) { 40 | conn, err := amqp.Dial("amqp://guest:guest@localhost:5672") 41 | if err != nil { 42 | t.Errorf("Could not establish a connection to AMQP server: %v", err) 43 | } 44 | 45 | defer conn.Close() 46 | } 47 | -------------------------------------------------------------------------------- /sender/sender.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/Pungyeon/go-rabbitmq-example/lib/event" 8 | "github.com/streadway/amqp" 9 | ) 10 | 11 | func main() { 12 | conn, err := amqp.Dial("amqp://guest:guest@localhost:5672") 13 | if err != nil { 14 | panic(err) 15 | } 16 | 17 | emitter, err := event.NewEventEmitter(conn) 18 | if err != nil { 19 | panic(err) 20 | } 21 | 22 | for i := 1; i < 10; i++ { 23 | emitter.Push(fmt.Sprintf("[%d] - %s", i, os.Args[1]), os.Args[1]) 24 | } 25 | } 26 | --------------------------------------------------------------------------------