├── .gitignore
├── LICENSE
├── README.md
├── _examples
└── main.go
├── jazz.go
├── jazz_test.go
└── struct.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, build with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2018 SOCIFI Ltd. code@socifi.com, vojtech.kletecka@socifi.com
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | 'Software'), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Jazz
2 |
3 | Abstraction layer for quick and simple rabbitMQ connection, messaging and administration. Inspired by Jazz Jackrabbit and his eternal hatred towards slow turtles.
4 |
5 |
6 |
7 |
8 |
9 |
10 | ## Usage
11 |
12 | This library contains three major parts - exchange/queue scheme creation, publishing of messages and consuming of messages. The greatest benefit of this partitioning is that each part might be in separate application. Also due to dedicated administration part, publishing and consuming of messages is simplified to great extent.
13 |
14 | ### Step 1: Connect to rabbit
15 |
16 | ```golang
17 | import(
18 | "github.com/socifi/jazz"
19 | )
20 |
21 | var dsn = "amqp://guest:guest@localhost:5672/"
22 |
23 | func main() {
24 | // ...
25 |
26 | c, err := jazz.Connect(dsn)
27 | if err != nil {
28 | t.Errorf("Could not connect to RabbitMQ: %v", err.Error())
29 | return
30 | }
31 |
32 | //...
33 | }
34 | ```
35 |
36 | ### Step 2: Create scheme
37 |
38 | Scheme specification is done via structure `Settings` which can be easily specified in YAML. So generally you need to decode YAML and then create all queues and exchanges
39 |
40 | It can be something really crazy like this!
41 |
42 | ```golang
43 | var data = []byte(`
44 | exchanges:
45 | exchange0:
46 | durable: true
47 | type: topic
48 | exchange1:
49 | durable: true
50 | type: topic
51 | bindings:
52 | - exchange: "exchange0"
53 | key: "key1"
54 | - exchange: "exchange0"
55 | key: "key2"
56 | exchange2:
57 | durable: true
58 | type: topic
59 | bindings:
60 | - exchange: "exchange0"
61 | key: "key3"
62 | - exchange: "exchange1"
63 | key: "key2"
64 | exchange3:
65 | durable: true
66 | type: topic
67 | bindings:
68 | - exchange: "exchange0"
69 | key: "key4"
70 | queues:
71 | queue0:
72 | durable: true
73 | bindings:
74 | - exchange: "exchange0"
75 | key: "key4"
76 | queue1:
77 | durable: true
78 | bindings:
79 | - exchange: "exchange1"
80 | key: "key2"
81 | queue2:
82 | durable: true
83 | bindings:
84 | - exchange: "exchange1"
85 | key: "#"
86 | queue3:
87 | durable: true
88 | bindings:
89 | - exchange: "exchange2"
90 | key: "#"
91 | queue4:
92 | durable: true
93 | bindings:
94 | - exchange: "exchange3"
95 | key: "#"
96 | queue5:
97 | durable: true
98 | bindings:
99 | - exchange: "exchange0"
100 | key: "#"
101 | `)
102 |
103 | func main() {
104 | // ...
105 |
106 | reader := bytes.NewReader(data)
107 | scheme, err := DecodeYaml(reader)
108 | if err != nil {
109 | t.Errorf("Could not read YAML: %v", err.Error())
110 | return
111 | }
112 |
113 | err = c.CreateScheme(scheme)
114 | if err != nil {
115 | t.Errorf("Could not create scheme: %v", err.Error())
116 | return
117 | }
118 |
119 | //...
120 |
121 | // Be nice and delete scheme (Not advisable in ).
122 | err = c.DeleteScheme(scheme)
123 | if err != nil {
124 | t.Errorf("Could not delete scheme: %v", err.Error())
125 | return
126 | }
127 | }
128 | ```
129 |
130 | ### Step 3: Publish and/or consume messages
131 |
132 | You can process each queue in separate application or everything together like this:
133 |
134 | ```golang
135 | func main() {
136 | // ...
137 |
138 | f := func(msg []byte) {
139 | fmt.Println(string(msg))
140 | }
141 |
142 | go c.ProcessQueue("queue1", f)
143 | go c.ProcessQueue("queue2", f)
144 | go c.ProcessQueue("queue3", f)
145 | go c.ProcessQueue("queue4", f)
146 | go c.ProcessQueue("queue5", f)
147 | go c.ProcessQueue("queue6", f)
148 | c.SendMessage("exchange0", "key1", "Hello World!")
149 | c.SendMessage("exchange0", "key2", "Hello!")
150 | c.SendMessage("exchange0", "key3", "World!")
151 | c.SendMessage("exchange0", "key4", "Hi!")
152 | c.SendMessage("exchange0", "key5", "Again!")
153 |
154 | //...
155 | }
156 | ```
157 |
158 | ## Notes
159 |
160 | No copyright infringement intended. The name Jazz Jackrabbit and artwork of Jazz Jackrabbit is intelectual property of Epic MegaGames and was taken over from [wikipedia](https://en.wikipedia.org/wiki/File:Jazz_Jackrabbit.jpg)
161 |
--------------------------------------------------------------------------------
/_examples/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "github.com/socifi/jazz"
7 | )
8 |
9 | var dsn = "amqp://guest:guest@localhost:5672/"
10 |
11 | var data = []byte(`
12 | exchanges:
13 | exchange0:
14 | durable: true
15 | type: topic
16 | exchange1:
17 | durable: true
18 | type: topic
19 | bindings:
20 | - exchange: "exchange0"
21 | key: "key1"
22 | - exchange: "exchange0"
23 | key: "key2"
24 | exchange2:
25 | durable: true
26 | type: topic
27 | bindings:
28 | - exchange: "exchange0"
29 | key: "key3"
30 | - exchange: "exchange1"
31 | key: "key2"
32 | exchange3:
33 | durable: true
34 | type: topic
35 | bindings:
36 | - exchange: "exchange0"
37 | key: "key4"
38 | queues:
39 | queue0:
40 | durable: true
41 | bindings:
42 | - exchange: "exchange0"
43 | key: "key4"
44 | queue1:
45 | durable: true
46 | bindings:
47 | - exchange: "exchange1"
48 | key: "key2"
49 | queue2:
50 | durable: true
51 | bindings:
52 | - exchange: "exchange1"
53 | key: "#"
54 | queue3:
55 | durable: true
56 | bindings:
57 | - exchange: "exchange2"
58 | key: "#"
59 | queue4:
60 | durable: true
61 | bindings:
62 | - exchange: "exchange3"
63 | key: "#"
64 | queue5:
65 | durable: true
66 | bindings:
67 | - exchange: "exchange0"
68 | key: "#"
69 | `)
70 |
71 | func main() {
72 | c, err := jazz.Connect(dsn)
73 | if err != nil {
74 | panic(fmt.Sprintf("Could not connect to RabbitMQ: %v", err.Error()))
75 | }
76 |
77 | reader := bytes.NewReader(data)
78 | scheme, err := jazz.DecodeYaml(reader)
79 | if err != nil {
80 | panic(fmt.Sprintf("Could not read YAML: %v", err.Error()))
81 | }
82 | // Create scheme
83 | err = c.CreateScheme(scheme)
84 | if err != nil {
85 | panic(fmt.Sprintf("Could not create scheme: %v", err.Error()))
86 | }
87 |
88 | // Handler function
89 | f := func(msg []byte) {
90 | fmt.Println(string(msg))
91 | }
92 |
93 | go c.ProcessQueue("queue0", f)
94 | go c.ProcessQueue("queue1", f)
95 | go c.ProcessQueue("queue2", f)
96 | go c.ProcessQueue("queue3", f)
97 | go c.ProcessQueue("queue4", f)
98 | go c.ProcessQueue("queue5", f)
99 | c.SendMessage("exchange0", "key1", "Hello World!")
100 | c.SendMessage("exchange0", "key2", "Hello!")
101 | c.SendMessage("exchange0", "key3", "World!")
102 | c.SendMessage("exchange0", "key4", "Hi!")
103 | c.SendMessage("exchange0", "key5", "Again!")
104 |
105 | // Be nice and clean up a little bit. Not advisable in production.
106 | err = c.DeleteScheme(scheme)
107 | if err != nil {
108 | panic(fmt.Sprintf("Could not delete scheme: %v", err.Error()))
109 | }
110 | c.Close()
111 | }
112 |
--------------------------------------------------------------------------------
/jazz.go:
--------------------------------------------------------------------------------
1 | package jazz
2 |
3 | import (
4 | "github.com/streadway/amqp"
5 | "gopkg.in/yaml.v2"
6 | "io"
7 | )
8 |
9 | // Connection is a struct which holds all necessary data for RabbitMQ connection
10 | type Connection struct {
11 | c *amqp.Connection
12 | }
13 |
14 | // Connect connects to RabbitMQ by dsn and return Connection object which uses openned connection during function calls issued later in code
15 | func Connect(dsn string) (*Connection, error) {
16 | conn, err := amqp.Dial(dsn)
17 | if err != nil {
18 | return nil, err
19 | }
20 | return &Connection{conn}, nil
21 | }
22 |
23 | // DecodeYaml reads yaml with specification of all exchanges and queues from io.Reader
24 | func DecodeYaml(r io.Reader) (Settings, error) {
25 | s := Settings{}
26 |
27 | dec := yaml.NewDecoder(r)
28 |
29 | err := dec.Decode(&s)
30 | if err != nil {
31 | return s, err
32 | }
33 | return s, nil
34 | }
35 |
36 | // CreateScheme creates all exchanges, queues and bindinges between them as specified in yaml string
37 | func (c *Connection) CreateScheme(s Settings) error {
38 | ch, err := c.c.Channel()
39 | if err != nil {
40 | return err
41 | }
42 |
43 | // Create exchanges according to settings
44 | for name, e := range s.Exchanges {
45 | err = ch.ExchangeDeclarePassive(name, e.Type, e.Durable, e.Autodelete, e.Internal, e.Nowait, nil)
46 | if err == nil {
47 | continue
48 | }
49 | ch, err = c.c.Channel()
50 | if err != nil {
51 | return err
52 | }
53 |
54 | err = ch.ExchangeDeclare(name, e.Type, e.Durable, e.Autodelete, e.Internal, e.Nowait, nil)
55 | if err != nil {
56 | return err
57 | }
58 | }
59 |
60 | // Create queues according to settings
61 | for name, q := range s.Queues {
62 | _, err := ch.QueueDeclarePassive(name, q.Durable, q.Autodelete, q.Exclusive, q.Nowait, nil)
63 | if err == nil {
64 | continue
65 | }
66 |
67 | ch, err = c.c.Channel()
68 | if err != nil {
69 | return err
70 | }
71 |
72 | _, err = ch.QueueDeclare(name, q.Durable, q.Autodelete, q.Exclusive, q.Nowait, nil)
73 | if err != nil {
74 | return err
75 | }
76 | }
77 |
78 | // Create bindings only now that everything is setup.
79 | // (If the bindings were created in one run together with exchanges and queues,
80 | // it would be possible to create binding to not yet existent queue.
81 | // This way it's still possible but now is an error on the user side)
82 | for name, e := range s.Exchanges {
83 | for _, b := range e.Bindings {
84 | err = ch.ExchangeBind(name, b.Key, b.Exchange, b.Nowait, nil)
85 | if err != nil {
86 | return err
87 | }
88 | }
89 | }
90 |
91 | for name, q := range s.Queues {
92 | for _, b := range q.Bindings {
93 | err = ch.QueueBind(name, b.Key, b.Exchange, b.Nowait, nil)
94 | if err != nil {
95 | return err
96 | }
97 | }
98 | }
99 |
100 | ch.Close()
101 | return nil
102 | }
103 |
104 | // DeleteScheme deletes all queues and exchanges (together with bindings) as specified in yaml string
105 | func (c *Connection) DeleteScheme(s Settings) error {
106 | ch, err := c.c.Channel()
107 | if err != nil {
108 | return err
109 | }
110 |
111 | for name := range s.Exchanges {
112 | err = ch.ExchangeDelete(name, false, false)
113 | if err != nil {
114 | return err
115 | }
116 | }
117 |
118 | for name := range s.Queues {
119 | _, err = ch.QueueDelete(name, false, false, false)
120 | if err != nil {
121 | return err
122 | }
123 | }
124 | ch.Close()
125 | return nil
126 | }
127 |
128 | // Close closes connection to RabbitMQ
129 | func (c *Connection) Close() error {
130 | return c.c.Close()
131 | }
132 |
133 | // SendMessage publishes plain text message to an exchange with specific routing key
134 | func (c *Connection) SendMessage(ex, key, msg string) error {
135 | ch, err := c.c.Channel()
136 | if err != nil {
137 | return err
138 | }
139 |
140 | err = ch.Publish(ex, key, false, false,
141 | amqp.Publishing{
142 | DeliveryMode: amqp.Persistent,
143 | ContentType: "text/plain",
144 | Body: []byte(msg),
145 | })
146 | if err != nil {
147 | return err
148 | }
149 | return ch.Close()
150 | }
151 |
152 | // SendBlob publishes byte blob message to an exchange with specific routing key
153 | func (c *Connection) SendBlob(ex, key string, msg []byte) error {
154 | ch, err := c.c.Channel()
155 | if err != nil {
156 | return err
157 | }
158 |
159 | err = ch.Publish(ex, key, false, false,
160 | amqp.Publishing{
161 | DeliveryMode: amqp.Persistent,
162 | ContentType: "application/octet-stream",
163 | Body: msg,
164 | })
165 | if err != nil {
166 | return err
167 | }
168 | return ch.Close()
169 | }
170 |
171 | // ProcessQueue calls handler function on each message delivered to a queue
172 | func (c *Connection) ProcessQueue(name string, f func([]byte)) error {
173 | ch, err := c.c.Channel()
174 | if err != nil {
175 | return err
176 | }
177 | msgs, err := ch.Consume(name, "", true, false, false, false, nil)
178 | if err != nil {
179 | return err
180 | }
181 | for d := range msgs {
182 | f(d.Body)
183 | }
184 | return nil
185 | }
186 |
--------------------------------------------------------------------------------
/jazz_test.go:
--------------------------------------------------------------------------------
1 | package jazz
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "testing"
7 | "time"
8 | )
9 |
10 | var dsn = "amqp://guest:guest@localhost:5672/"
11 |
12 | var data = []byte(`
13 | exchanges:
14 | exchange0:
15 | durable: true
16 | type: topic
17 | exchange1:
18 | durable: true
19 | type: topic
20 | bindings:
21 | - exchange: "exchange0"
22 | key: "key1"
23 | - exchange: "exchange0"
24 | key: "key2"
25 | exchange2:
26 | durable: true
27 | type: topic
28 | bindings:
29 | - exchange: "exchange0"
30 | key: "key3"
31 | - exchange: "exchange1"
32 | key: "key2"
33 | exchange3:
34 | durable: true
35 | type: topic
36 | bindings:
37 | - exchange: "exchange0"
38 | key: "key4"
39 | queues:
40 | queue0:
41 | durable: true
42 | bindings:
43 | - exchange: "exchange0"
44 | key: "key4"
45 | queue1:
46 | durable: true
47 | bindings:
48 | - exchange: "exchange1"
49 | key: "key2"
50 | queue2:
51 | durable: true
52 | bindings:
53 | - exchange: "exchange1"
54 | key: "#"
55 | queue3:
56 | durable: true
57 | bindings:
58 | - exchange: "exchange2"
59 | key: "#"
60 | queue4:
61 | durable: true
62 | bindings:
63 | - exchange: "exchange3"
64 | key: "#"
65 | queue5:
66 | durable: true
67 | bindings:
68 | - exchange: "exchange0"
69 | key: "#"
70 | `)
71 |
72 | func TestConnection(t *testing.T) {
73 | c, err := Connect(dsn)
74 | if err != nil {
75 | t.Errorf("Could not connect to RabbitMQ: %v", err.Error())
76 | return
77 | }
78 | c.Close()
79 | }
80 |
81 | func TestSchemeCreation(t *testing.T) {
82 | c, err := Connect(dsn)
83 | if err != nil {
84 | t.Errorf("Could not connect to RabbitMQ: %v", err.Error())
85 | return
86 | }
87 | r := bytes.NewReader(data)
88 | s, err := DecodeYaml(r)
89 | if err != nil {
90 | t.Errorf("Could not read YAML: %v", err.Error())
91 | return
92 | }
93 |
94 | err = c.DeleteScheme(s)
95 | if err != nil {
96 | t.Errorf("Could not delete scheme: %v", err.Error())
97 | return
98 | }
99 | err = c.CreateScheme(s)
100 | if err != nil {
101 | t.Errorf("Could not create scheme: %v", err.Error())
102 | return
103 | }
104 | err = c.DeleteScheme(s)
105 | if err != nil {
106 | t.Errorf("Could not delete scheme: %v", err.Error())
107 | return
108 | }
109 | c.Close()
110 | }
111 |
112 | func TestSendMessage(t *testing.T) {
113 | c, err := Connect(dsn)
114 | if err != nil {
115 | t.Errorf("Could not connect to RabbitMQ: %v", err.Error())
116 | return
117 | }
118 |
119 | reader := bytes.NewReader(data)
120 | scheme, err := DecodeYaml(reader)
121 | if err != nil {
122 | t.Errorf("Could not read YAML: %v", err.Error())
123 | return
124 | }
125 |
126 | err = c.CreateScheme(scheme)
127 | if err != nil {
128 | t.Errorf("Could not create scheme: %v", err.Error())
129 | return
130 | }
131 |
132 | f := func(msg []byte) {
133 | fmt.Println(string(msg))
134 | }
135 |
136 | go c.ProcessQueue("queue0", f)
137 | go c.ProcessQueue("queue1", f)
138 | go c.ProcessQueue("queue2", f)
139 | go c.ProcessQueue("queue3", f)
140 | go c.ProcessQueue("queue4", f)
141 | go c.ProcessQueue("queue5", f)
142 | c.SendMessage("exchange0", "key1", "Hello World!")
143 | c.SendMessage("exchange0", "key2", "Hello!")
144 | c.SendMessage("exchange0", "key3", "World!")
145 | c.SendMessage("exchange0", "key4", "Hi!")
146 | c.SendMessage("exchange0", "key5", "Again!")
147 |
148 | time.Sleep(time.Second)
149 |
150 | err = c.DeleteScheme(scheme)
151 | if err != nil {
152 | t.Errorf("Could not delete scheme: %v", err.Error())
153 | return
154 | }
155 |
156 | c.Close()
157 | }
158 |
--------------------------------------------------------------------------------
/struct.go:
--------------------------------------------------------------------------------
1 | package jazz
2 |
3 | // Exchange is structure with specification of properties of RabbitMQ exchange
4 | type Exchange struct {
5 | Durable bool `yaml:"durable"`
6 | Autodelete bool `yaml:"autodelete"`
7 | Internal bool `yaml:"internal"`
8 | Nowait bool `yaml:"nowait"`
9 | Type string `yaml:"type"`
10 | Bindings []Binding `yaml:"bindings"`
11 | }
12 |
13 | // Binding specifies to which exchange should be an exchange or a queue binded
14 | type Binding struct {
15 | Exchange string `yaml:"exchange"`
16 | Key string `yaml:"key"`
17 | Nowait bool `yaml:"nowait"`
18 | }
19 |
20 | // QueueSpec is a specification of properties of RabbitMQ queue
21 | type QueueSpec struct {
22 | Durable bool `yaml:"durable"`
23 | Autodelete bool `yaml:"autodelete"`
24 | Nowait bool `yaml:"nowait"`
25 | Exclusive bool `yaml:"exclusive"`
26 | Bindings []Binding `yaml:"bindings"`
27 | }
28 |
29 | // Settings is a specification of all queues and exchanges together with all bindings.
30 | type Settings struct {
31 | Exchanges map[string]Exchange `yaml:"exchanges"`
32 | Queues map[string]QueueSpec `yaml:"queues"`
33 | }
34 |
--------------------------------------------------------------------------------