├── .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 | Jazz Jackrabbit 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 | --------------------------------------------------------------------------------