├── context.go ├── .gitignore ├── error.go ├── .travis.yml ├── .vscode └── launch.json ├── reply.go ├── examples ├── sub.go └── request.go ├── LICENSE ├── BENCHMARK.md ├── README.md ├── hemera_test.go ├── router ├── router.go └── router_test.go └── hemera.go /context.go: -------------------------------------------------------------------------------- 1 | package hemera 2 | 3 | type Context struct { 4 | Meta Meta 5 | Delegate Delegate 6 | Trace Trace 7 | Error error 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | 16 | debug -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package hemera 2 | 3 | type ( 4 | Error struct { 5 | Name string `json:"name"` 6 | Message string `json:"message"` 7 | Code int16 `json:"code"` 8 | } 9 | ) 10 | 11 | func NewError(name, message string, code int16) *Error { 12 | return &Error{ 13 | Name: name, 14 | Message: message, 15 | Code: code, 16 | } 17 | } 18 | 19 | func NewErrorSimple(message string) *Error { 20 | return &Error{ 21 | Message: message, 22 | } 23 | } 24 | 25 | func (e *Error) Error() string { 26 | return e.Message 27 | } 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | go: 4 | - 1.8.3 5 | install: 6 | - go get -t ./... 7 | - go get github.com/nats-io/gnatsd 8 | - go get github.com/nats-io/go-nats 9 | - go get github.com/nats-io/nuid 10 | - go get github.com/fatih/structs 11 | - go get github.com/mitchellh/mapstructure 12 | - go get github.com/stretchr/testify/assert 13 | - go get -u honnef.co/go/tools/cmd/staticcheck 14 | - go get -u honnef.co/go/tools/cmd/gosimple 15 | - go get -u github.com/client9/misspell/cmd/misspell 16 | before_script: 17 | - go fmt ./... 18 | - go vet ./... 19 | - gosimple ./... 20 | script: 21 | - go test -i -race ./... 22 | - go test -v -race ./... -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch Tests", 6 | "type": "go", 7 | "request": "launch", 8 | "mode": "test", 9 | "remotePath": "", 10 | "port": 2346, 11 | "host": "127.0.0.1", 12 | "program": "${workspaceRoot}", 13 | "env": {}, 14 | "args": [], 15 | "showLog": true 16 | }, 17 | { 18 | "name": "Launch", 19 | "type": "go", 20 | "request": "launch", 21 | "mode": "debug", 22 | "remotePath": "", 23 | "port": 2345, 24 | "host": "127.0.0.1", 25 | "program": "${workspaceRoot}/examples/sub.go", 26 | "env": {}, 27 | "args": [], 28 | "showLog": true 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /reply.go: -------------------------------------------------------------------------------- 1 | package hemera 2 | 3 | import ( 4 | "github.com/json-iterator/go" 5 | "github.com/nats-io/nuid" 6 | ) 7 | 8 | type Reply struct { 9 | hemera *Hemera 10 | pattern interface{} 11 | context *Context 12 | reply string 13 | } 14 | 15 | func (r *Reply) Send(payload interface{}) { 16 | response := packet{ 17 | Pattern: r.pattern, 18 | Meta: r.context.Meta, 19 | Trace: r.context.Trace, 20 | Request: request{ 21 | ID: nuid.Next(), 22 | RequestType: RequestType, 23 | }, 24 | } 25 | 26 | // Check if error or message was passed 27 | he, ok := payload.(Error) 28 | if ok { 29 | response.Error = &he 30 | } else { 31 | response.Result = payload 32 | } 33 | 34 | data, _ := jsoniter.Marshal(&response) 35 | r.hemera.Conn.Publish(r.reply, data) 36 | } 37 | -------------------------------------------------------------------------------- /examples/sub.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | "runtime" 9 | 10 | server "github.com/hemerajs/go-hemera" 11 | nats "github.com/nats-io/go-nats" 12 | ) 13 | 14 | type MathPattern struct { 15 | Topic string `json:"topic"` 16 | Cmd string `json:"cmd"` 17 | } 18 | 19 | type RequestPattern struct { 20 | Topic string `json:"topic" mapstructure:"topic"` 21 | Cmd string `json:"cmd" mapstructure:"cmd"` 22 | A int `json:"a" mapstructure:"a"` 23 | B int `json:"b" mapstructure:"b"` 24 | } 25 | 26 | func main() { 27 | nc, err := nats.Connect(nats.DefaultURL) 28 | 29 | if err != nil { 30 | log.Fatalf("Can't connect: %v\n", err) 31 | } 32 | 33 | hemera, _ := server.CreateHemera(nc) 34 | 35 | pattern := MathPattern{ Topic: "math", Cmd: "add" } 36 | 37 | hemera.Add(pattern, func(req *RequestPattern, reply server.Reply, context server.Context) { 38 | fmt.Printf("Request: %+v\n", req) 39 | reply.Send(req.A + req.B) 40 | }) 41 | 42 | log.Printf("Listening on \n") 43 | 44 | runtime.Goexit() 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 HemeraJs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/request.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | "runtime" 9 | 10 | server "github.com/hemerajs/go-hemera" 11 | nats "github.com/nats-io/go-nats" 12 | ) 13 | 14 | type MathPattern struct { 15 | Topic string 16 | Cmd string 17 | } 18 | 19 | type RequestPattern struct { 20 | Topic string 21 | Cmd string 22 | A int 23 | B int 24 | Meta server.Meta 25 | Delegate server.Delegate 26 | } 27 | 28 | type Response struct { 29 | Result int 30 | } 31 | 32 | func main() { 33 | nc, err := nats.Connect(nats.DefaultURL) 34 | 35 | if err != nil { 36 | log.Fatalf("Can't connect: %v\n", err) 37 | } 38 | 39 | hemera, _ := server.CreateHemera(nc) 40 | 41 | pattern := MathPattern{Topic: "math", Cmd: "add"} 42 | 43 | hemera.Add(pattern, func(req *RequestPattern, reply server.Reply, context *server.Context) { 44 | fmt.Printf("Request: %+v\n", req) 45 | result := Response{Result: req.A + req.B} 46 | reply.Send(result) 47 | }) 48 | 49 | requestPattern := RequestPattern{ 50 | Topic: "math", 51 | Cmd: "add", 52 | A: 1, 53 | B: 2, 54 | Meta: server.Meta{"Test": 1}, 55 | Delegate: server.Delegate{"Test": 2}, 56 | } 57 | 58 | res := &Response{} 59 | hemera.Act(requestPattern, res) 60 | 61 | fmt.Printf("Response %+v", res) 62 | 63 | runtime.Goexit() 64 | } 65 | -------------------------------------------------------------------------------- /BENCHMARK.md: -------------------------------------------------------------------------------- 1 | # Router 2 | 3 | #### Depth order 4 | ``` 5 | a: AddPattern{ Topic: "order" } 6 | b: AddPattern{ Topic: "order", Cmd: "create" } 7 | c: AddPattern{ Topic: "order", Cmd: "create", Type: 3 } 8 | 9 | ActPattern{ Topic: "order", Cmd: "create" } // b Matched 10 | ActPattern{ Topic: "order" } // a Matched 11 | ActPattern{ Topic: "order", Type: 3 } // c Matched 12 | ``` 13 | 14 | #### Insertion order 15 | ``` 16 | a: AddPattern{ Topic: "order" } 17 | b: AddPattern{ Topic: "order", Cmd: "create" } 18 | c: AddPattern{ Topic: "order", Cmd: "create", Type: 3 } 19 | 20 | ActPattern{ Topic: "order", Cmd: "create" } // a Matched 21 | ActPattern{ Topic: "order" } // a Matched 22 | ActPattern{ Topic: "order", Type: 3 } // a Matched 23 | ``` 24 | 25 | ## Benchmark 26 | - `Lookup` on 10000 Pattern 27 | - `List` on 10000 Pattern 28 | - `Add` with struct of depth 4 29 | ``` 30 | BenchmarkLookupWeightDepth7-4 200000 7236 ns/op 31 | BenchmarkLookupWeightDepth6-4 10000 139158 ns/op 32 | BenchmarkLookupWeightDepth5-4 5000 281219 ns/op 33 | BenchmarkLookupWeightDepth4-4 2000 705551 ns/op 34 | BenchmarkLookupWeightDepth3-4 2000 557297 ns/op 35 | BenchmarkLookupWeightDepth2-4 2000 690949 ns/op 36 | BenchmarkLookupWeightDepth1-4 2000 682166 ns/op 37 | BenchmarkListDepth100000-4 500 2504608 ns/op 38 | BenchmarkAddDepth-4 10000 128326 ns/op 39 | BenchmarkLookupWeightInsertion7-4 200000 7424 ns/op 40 | BenchmarkLookupWeightInsertion6-4 200000 7020 ns/op 41 | BenchmarkLookupWeightInsertion5-4 200000 6845 ns/op 42 | BenchmarkLookupWeightInsertion4-4 200000 6480 ns/op 43 | BenchmarkLookupWeightInsertion3-4 200000 6355 ns/op 44 | BenchmarkLookupWeightInsertion2-4 200000 5895 ns/op 45 | BenchmarkLookupWeightInsertion1-4 3000 468402 ns/op 46 | BenchmarkListInsertion10000-4 500 2627245 ns/op 47 | BenchmarkAddInsertion-4 10000 734603 ns/op 48 | PASS 49 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Hemera 3 |

4 | 5 |

6 | Build Status 7 | License MIT 8 |

9 | 10 |

11 | A Go microservices toolkit for the NATS messaging system 12 |

13 | 14 | **Status:** Experimental 15 | 16 | ## Install 17 | 18 | ``` 19 | go get ./.. 20 | go get github.com/nats-io/gnatsd/server 21 | ``` 22 | 23 | ### Example 24 | ```go 25 | 26 | type MathPattern struct { 27 | Topic string 28 | Cmd string 29 | } 30 | 31 | type RequestPattern struct { 32 | Topic string 33 | Cmd string 34 | A int 35 | B int 36 | Meta server.Meta 37 | Delegate server.Delegate 38 | } 39 | 40 | type Response struct { 41 | Result int 42 | } 43 | 44 | nc, _ := nats.Connect(nats.DefaultURL) 45 | 46 | hemera, _ := server.CreateHemera(nc, server.Timeout(2000), server.IndexingStrategy(DepthIndexing)...) 47 | 48 | // Define the pattern of your action 49 | pattern := MathPattern{Topic: "math", Cmd: "add"} 50 | hemera.Add(pattern, func(req *RequestPattern, reply server.Reply, context *server.Context) { 51 | // Build response 52 | result := Response{Result: req.A + req.B} 53 | // Add meta informations 54 | context.Meta["key"] = "value" 55 | // Send it back 56 | reply.Send(result) 57 | }) 58 | 59 | // Define the call of your RPC 60 | requestPattern := RequestPattern{ 61 | Topic: "math", 62 | Cmd: "add", 63 | A: 1, 64 | B: 2, 65 | Meta: server.Meta{ "Test": 1 }, 66 | Delegate: server.Delegate{ "Test": 2 }, 67 | } 68 | 69 | res := &Response{} // Pointer to struct 70 | ctx := hemera.Act(requestPattern, res) 71 | 72 | res = &Response{} 73 | ctx = hemera.Act(requestPattern, res, ctx) 74 | 75 | log.Printf("Response %+v", res) 76 | ``` 77 | 78 | ## Pattern matching 79 | We implemented two indexing strategys 80 | - `depth order` match the entry with the most properties first. 81 | - `insertion order` match the entry with the least properties first. `(default)` 82 | 83 | ## TODO 84 | - [X] Setup nats server for testing 85 | - [X] Implement Add and Act 86 | - [X] Create Context (trace, meta, delegate) structures 87 | - [X] Use tree for pattern indexing 88 | - [X] Support indexing by depth order 89 | - [X] Support indexing by insetion order 90 | - [X] Clean request pattern from none primitive values 91 | - [X] Meta & Delegate support 92 | - [X] Implement basic pattern matching (router) 93 | - [ ] Implement router `remove` method 94 | 95 | ## Credits 96 | 97 | - [Bloomrun](https://github.com/mcollina/bloomrun) the pattern matching library for NodeJs 98 | -------------------------------------------------------------------------------- /hemera_test.go: -------------------------------------------------------------------------------- 1 | package hemera 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | natsServer "github.com/nats-io/gnatsd/server" 9 | gnatsd "github.com/nats-io/gnatsd/test" 10 | nats "github.com/nats-io/go-nats" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | const TEST_PORT = 8368 15 | 16 | var reconnectOpts = nats.Options{ 17 | Url: fmt.Sprintf("nats://localhost:%d", TEST_PORT), 18 | AllowReconnect: true, 19 | MaxReconnect: 10, 20 | ReconnectWait: 100 * time.Millisecond, 21 | Timeout: nats.DefaultTimeout, 22 | } 23 | 24 | func RunServerOnPort(port int) *natsServer.Server { 25 | opts := gnatsd.DefaultTestOptions 26 | opts.Port = port 27 | return RunServerWithOptions(opts) 28 | } 29 | 30 | func RunServerWithOptions(opts natsServer.Options) *natsServer.Server { 31 | return gnatsd.RunServer(&opts) 32 | } 33 | 34 | type MathPattern struct { 35 | Topic string 36 | Cmd string 37 | } 38 | 39 | type RequestPattern struct { 40 | Topic string 41 | Cmd string 42 | A int 43 | B int 44 | } 45 | 46 | type Response struct { 47 | Result int 48 | } 49 | 50 | func TestCreateHemera(t *testing.T) { 51 | assert := assert.New(t) 52 | 53 | ts := RunServerOnPort(TEST_PORT) 54 | defer ts.Shutdown() 55 | 56 | opts := reconnectOpts 57 | nc, err := opts.Connect() 58 | defer nc.Close() 59 | 60 | if err != nil { 61 | panic(err) 62 | } 63 | 64 | h, _ := CreateHemera(nc) 65 | 66 | assert.NotEqual(h, nil, "they should not nil") 67 | 68 | } 69 | 70 | func TestAdd(t *testing.T) { 71 | assert := assert.New(t) 72 | 73 | ts := RunServerOnPort(TEST_PORT) 74 | defer ts.Shutdown() 75 | 76 | opts := reconnectOpts 77 | nc, err := opts.Connect() 78 | defer nc.Close() 79 | 80 | if err != nil { 81 | panic(err) 82 | } 83 | 84 | h, _ := CreateHemera(nc) 85 | 86 | pattern := MathPattern{Topic: "math", Cmd: "add"} 87 | 88 | h.Add(pattern, func(req *RequestPattern, reply Reply, context *Context) { 89 | reply.Send(Response{Result: req.A + req.B}) 90 | }) 91 | 92 | assert.Equal(len(h.Router.List()), 1, "Should be 1") 93 | 94 | } 95 | 96 | func TestActRequest(t *testing.T) { 97 | ts := RunServerOnPort(TEST_PORT) 98 | defer ts.Shutdown() 99 | 100 | opts := reconnectOpts 101 | nc, err := opts.Connect() 102 | defer nc.Close() 103 | 104 | if err != nil { 105 | panic(err) 106 | } 107 | 108 | h, _ := CreateHemera(nc) 109 | 110 | pattern := MathPattern{Topic: "math", Cmd: "add"} 111 | 112 | h.Add(pattern, func(req *RequestPattern, reply Reply, context *Context) { 113 | reply.Send(Response{Result: req.A + req.B}) 114 | }) 115 | 116 | requestPattern := RequestPattern{Topic: "math", Cmd: "add", A: 1, B: 2} 117 | res := &Response{} 118 | h.Act(requestPattern, res) 119 | 120 | assert.Equal(t, res.Result, 3, "Should be 3") 121 | 122 | } 123 | 124 | func TestNoDuplicatesAllowed(t *testing.T) { 125 | assert := assert.New(t) 126 | 127 | ts := RunServerOnPort(TEST_PORT) 128 | defer ts.Shutdown() 129 | 130 | opts := reconnectOpts 131 | nc, err := opts.Connect() 132 | defer nc.Close() 133 | 134 | if err != nil { 135 | panic(err) 136 | } 137 | 138 | h, _ := CreateHemera(nc) 139 | 140 | pattern := MathPattern{Topic: "math", Cmd: "add"} 141 | 142 | h.Add(pattern, func(req *RequestPattern, reply Reply, context Context) { 143 | reply.Send(Response{Result: req.A + req.B}) 144 | }) 145 | 146 | _, errAdd := h.Add(pattern, func(req *RequestPattern, reply Reply, context Context) { 147 | reply.Send(Response{Result: req.A + req.B}) 148 | }) 149 | 150 | assert.Equal(errAdd.Error(), "add: duplicate pattern", "Should be not allowed to add duplicate patterns") 151 | 152 | } 153 | -------------------------------------------------------------------------------- /router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "math" 5 | "reflect" 6 | "sort" 7 | "strings" 8 | 9 | "github.com/emirpasic/gods/maps/hashmap" 10 | "github.com/emirpasic/gods/sets/hashset" 11 | "github.com/fatih/structs" 12 | ) 13 | 14 | type PatternFieldValue interface{} 15 | type PatternFields map[string]PatternFieldValue 16 | 17 | type PatternSet struct { 18 | Pattern interface{} 19 | Weight int 20 | Fields PatternFields 21 | Payload interface{} 22 | } 23 | 24 | type PatternSets []*PatternSet 25 | 26 | type Bucket struct { 27 | PatternSets PatternSets 28 | Weight int 29 | } 30 | 31 | type Router struct { 32 | Map *hashmap.Map 33 | Buckets []*Bucket 34 | IsDeep bool 35 | insertCount int 36 | } 37 | 38 | //NewRouter creaet a new router 39 | func NewRouter(IsDeep bool) *Router { 40 | hm := hashmap.New() 41 | 42 | return &Router{Map: hm, IsDeep: IsDeep} 43 | } 44 | 45 | // Add Insert a new pattern 46 | func (r *Router) Add(pattern, payload interface{}) { 47 | ps := r.convertToPatternSet(pattern) 48 | ps.Payload = payload 49 | 50 | for key, val := range ps.Fields { 51 | // create map to save key -> value pair 52 | if _, ok := r.Map.Get(key); !ok { 53 | r.Map.Put(key, hashmap.New()) 54 | } 55 | 56 | patternField, _ := r.Map.Get(key) 57 | patternValueMap := patternField.(*hashmap.Map) 58 | patternValueMapValue, ok := patternValueMap.Get(val) 59 | 60 | var bucket *Bucket 61 | 62 | if !ok { 63 | 64 | // fmt.Printf("Create bucket Key: '%+v' Value: '%+v'\n", key, val) 65 | bucket = &Bucket{} 66 | 67 | if r.IsDeep { 68 | bucket.Weight = 0 69 | } else { 70 | bucket.Weight = math.MaxInt32 71 | } 72 | 73 | patternValueMap.Put(val, bucket) 74 | 75 | r.Buckets = append(r.Buckets, bucket) 76 | } else { 77 | bucket = patternValueMapValue.(*Bucket) 78 | } 79 | 80 | if r.IsDeep { 81 | if bucket.Weight < ps.Weight { 82 | bucket.Weight = ps.Weight 83 | } 84 | } else { 85 | if bucket.Weight > ps.Weight { 86 | bucket.Weight = ps.Weight 87 | } 88 | } 89 | 90 | // pattern to bucket 91 | bucket.PatternSets = append(bucket.PatternSets, ps) 92 | 93 | //sort buckets of pattern 94 | if r.IsDeep { 95 | sort.Slice(bucket.PatternSets, func(i int, j int) bool { 96 | return bucket.PatternSets[i].Weight > bucket.PatternSets[j].Weight 97 | }) 98 | } else { 99 | sort.Slice(bucket.PatternSets, func(i int, j int) bool { 100 | return bucket.PatternSets[i].Weight < bucket.PatternSets[j].Weight 101 | }) 102 | } 103 | } 104 | 105 | } 106 | 107 | func (r *Router) List() PatternSets { 108 | list := PatternSets{} 109 | visited := hashset.New() 110 | 111 | for _, key := range r.Map.Keys() { 112 | 113 | val, _ := r.Map.Get(key) 114 | pv := val.(*hashmap.Map) 115 | 116 | for _, o := range pv.Keys() { 117 | 118 | b, ok := pv.Get(o) 119 | 120 | if ok { 121 | ps := b.(*Bucket) 122 | 123 | for _, p := range ps.PatternSets { 124 | 125 | if !visited.Contains(p) { 126 | visited.Add(p) 127 | list = append(list, p) 128 | } 129 | 130 | } 131 | } 132 | 133 | } 134 | 135 | } 136 | 137 | return list 138 | } 139 | 140 | // FieldsArrayEquals check if b is subset of a 141 | func FieldsArrayEquals(a PatternFields, b PatternFields) bool { 142 | for key, field := range b { 143 | if a[key] != field { 144 | return false 145 | } 146 | } 147 | 148 | return true 149 | } 150 | 151 | // equals is shorthand for FieldsArrayEquals 152 | func equals(a *PatternSet, b *PatternSet) bool { 153 | return FieldsArrayEquals(a.Fields, b.Fields) 154 | } 155 | 156 | // Lookup Search for a specific pattern and returns it 157 | func (r *Router) Lookup(p interface{}) *PatternSet { 158 | 159 | ps := r.convertToPatternSet(p) 160 | 161 | buckets := []*Bucket{} 162 | 163 | for key, val := range ps.Fields { 164 | 165 | // return e.g value of "topic" 166 | patternField, ok := r.Map.Get(key) 167 | 168 | // when key was not indexed 169 | if !ok { 170 | continue 171 | } 172 | 173 | // convert value to hashMap 174 | patternValueMap := patternField.(*hashmap.Map) 175 | 176 | // return bucket of e.g "topic" -> "math" -> bucket 177 | patternValueMapValue, ok := patternValueMap.Get(val) 178 | 179 | if ok { 180 | b, ok := patternValueMapValue.(*Bucket) 181 | 182 | if !ok { 183 | panic("Value is not from type *Bucket") 184 | } 185 | 186 | buckets = append(buckets, b) 187 | 188 | //sort buckets 189 | if r.IsDeep { 190 | sort.Slice(buckets, func(i int, j int) bool { 191 | return buckets[i].Weight > buckets[j].Weight 192 | }) 193 | } else { 194 | sort.Slice(buckets, func(i int, j int) bool { 195 | return buckets[i].Weight < buckets[j].Weight 196 | }) 197 | } 198 | 199 | } 200 | } 201 | 202 | /* for _, x := range buckets { 203 | fmt.Printf("Bucket %+v\n", x) 204 | for _, p := range x.PatternSets { 205 | fmt.Printf(" Set: %+v\n", p) 206 | } 207 | 208 | } */ 209 | 210 | var matched bool 211 | 212 | for _, bucket := range buckets { 213 | for _, pattern := range bucket.PatternSets { 214 | 215 | matched = equals(ps, pattern) 216 | 217 | if matched { 218 | return pattern 219 | } 220 | 221 | } 222 | } 223 | 224 | return nil 225 | 226 | } 227 | 228 | // convertToPatternSet convert a struct to a patternset 229 | func (r *Router) convertToPatternSet(p interface{}) *PatternSet { 230 | fields := structs.Fields(p) 231 | 232 | ps := &PatternSet{} 233 | ps.Fields = make(PatternFields) 234 | ps.Pattern = p 235 | ps.Weight = 0 236 | 237 | for _, field := range fields { 238 | if !strings.HasSuffix(field.Name(), "_") && !field.IsZero() { 239 | fieldKind := field.Kind() 240 | 241 | switch fieldKind { 242 | case reflect.Int8: 243 | fallthrough 244 | case reflect.Int16: 245 | fallthrough 246 | case reflect.Int32: 247 | fallthrough 248 | case reflect.Int64: 249 | fallthrough 250 | case reflect.Int: 251 | fallthrough 252 | case reflect.String: 253 | fallthrough 254 | case reflect.Bool: 255 | fallthrough 256 | case reflect.Float32: 257 | fallthrough 258 | case reflect.Float64: 259 | ps.Fields[field.Name()] = field.Value() 260 | ps.Weight++ 261 | } 262 | } 263 | } 264 | 265 | // sort by insertion order 266 | if !r.IsDeep { 267 | r.insertCount++ 268 | ps.Weight = r.insertCount 269 | } 270 | 271 | return ps 272 | } 273 | -------------------------------------------------------------------------------- /hemera.go: -------------------------------------------------------------------------------- 1 | package hemera 2 | 3 | import ( 4 | "log" 5 | "reflect" 6 | "time" 7 | 8 | "github.com/fatih/structs" 9 | "github.com/hemerajs/go-hemera/router" 10 | jsoniter "github.com/json-iterator/go" 11 | "github.com/mitchellh/mapstructure" 12 | nats "github.com/nats-io/go-nats" 13 | "github.com/nats-io/nuid" 14 | ) 15 | 16 | const ( 17 | // RequestType represent the request with default request / reply semantic 18 | RequestType = "request" 19 | // PubsubType represent the request with publish / subscribe semantic 20 | PubsubType = "pubsub" 21 | // RequestTimeout is the maxiumum act timeout in miliseconds 22 | RequestTimeout = 2000 23 | DepthIndexing = true 24 | InsertionIndexing = false 25 | ) 26 | 27 | type ( 28 | // Option is a function on the options for hemera 29 | Option func(*Options) error 30 | Options struct { 31 | Timeout time.Duration 32 | IndexingStrategy bool 33 | } 34 | Handler interface{} 35 | Hemera struct { 36 | Conn *nats.Conn 37 | Router *router.Router 38 | Opts Options 39 | } 40 | request struct { 41 | ID string `json:"id"` 42 | RequestType string `json:"type"` 43 | } 44 | Trace struct { 45 | TraceID string `json:"traceId"` 46 | ParentSpanID string `json:"parentSpanId"` 47 | SpanID string `json:"spanId"` 48 | Timestamp int64 `json:"timestamp"` 49 | Service string `json:"service"` 50 | Method string `json:"method"` 51 | Duration int64 `json:"duration"` 52 | } 53 | packet struct { 54 | Pattern interface{} `json:"pattern"` 55 | Meta Meta `json:"meta"` 56 | Delegate Delegate `json:"delegate"` 57 | Result interface{} `json:"result"` 58 | Trace Trace `json:"trace"` 59 | Request request `json:"request"` 60 | Error *Error `json:"error"` 61 | } 62 | Meta map[string]interface{} 63 | Delegate map[string]interface{} 64 | ) 65 | 66 | func GetDefaultOptions() Options { 67 | opts := Options{ 68 | Timeout: RequestTimeout, 69 | IndexingStrategy: false, 70 | } 71 | return opts 72 | } 73 | 74 | // New create a new Hemera struct 75 | func CreateHemera(conn *nats.Conn, options ...Option) (Hemera, error) { 76 | opts := GetDefaultOptions() 77 | for _, opt := range options { 78 | if err := opt(&opts); err != nil { 79 | return Hemera{Opts: opts, Router: router.NewRouter(opts.IndexingStrategy)}, err 80 | } 81 | } 82 | return Hemera{Conn: conn, Opts: opts, Router: router.NewRouter(opts.IndexingStrategy)}, nil 83 | } 84 | 85 | // Timeout is an Option to set the timeout for a act request 86 | func Timeout(t time.Duration) Option { 87 | return func(o *Options) error { 88 | o.Timeout = t 89 | return nil 90 | } 91 | } 92 | 93 | func IndexingStrategy(isDeep bool) Option { 94 | return func(o *Options) error { 95 | o.IndexingStrategy = isDeep 96 | return nil 97 | } 98 | } 99 | 100 | // Add is a method to subscribe on a specific topic 101 | func (h *Hemera) Add(p interface{}, cb Handler) (*nats.Subscription, error) { 102 | s := structs.New(p) 103 | f := s.Field("Topic") 104 | 105 | if f.IsZero() { 106 | return nil, NewErrorSimple("add: topic is required") 107 | } 108 | 109 | topic, ok := f.Value().(string) 110 | 111 | if !ok { 112 | return nil, NewErrorSimple("add: topic must be from type string") 113 | } 114 | 115 | // Get the types of the Add handler args 116 | argTypes, numArgs := ArgInfo(cb) 117 | 118 | if numArgs < 2 { 119 | return nil, NewErrorSimple("add: invalid add handler arguments") 120 | } 121 | 122 | lp := h.Router.Lookup(p) 123 | 124 | if lp != nil { 125 | return nil, NewErrorSimple("add: duplicate pattern") 126 | } 127 | 128 | h.Router.Add(p, cb) 129 | 130 | // Response struct 131 | argMsgType := argTypes[0] 132 | 133 | sub, err := h.Conn.QueueSubscribe(topic, topic, func(m *nats.Msg) { 134 | h.callAddAction(topic, m, argMsgType, numArgs) 135 | }) 136 | 137 | if err != nil { 138 | return nil, err 139 | } 140 | 141 | return sub, nil 142 | } 143 | 144 | func (h *Hemera) callAddAction(topic string, m *nats.Msg, mContainer reflect.Type, numArgs int) { 145 | var oPtr reflect.Value 146 | 147 | if mContainer.Kind() != reflect.Ptr { 148 | oPtr = reflect.New(mContainer) 149 | } else { 150 | oPtr = reflect.New(mContainer.Elem()) 151 | } 152 | 153 | pack := packet{} 154 | 155 | // decoding hemera packet 156 | jsoniter.Unmarshal(m.Data, &pack) 157 | 158 | context := &Context{Trace: pack.Trace, Meta: pack.Meta, Delegate: pack.Delegate} 159 | 160 | oContextPtr := reflect.ValueOf(context) 161 | 162 | // Pattern is the request 163 | o := pack.Pattern 164 | 165 | // return the value of oPtr as an interface{} 166 | oi := oPtr.Interface() 167 | 168 | // Decode map to struct 169 | err := mapstructure.Decode(o, oi) 170 | 171 | if err != nil { 172 | panic(err) 173 | } 174 | 175 | e := oPtr.Elem().Interface() 176 | 177 | p := h.Router.Lookup(e) 178 | 179 | if p != nil { 180 | // Get "Value" of the reply callback for the reflection Call 181 | reply := Reply{ 182 | context: context, 183 | pattern: p.Pattern, 184 | reply: m.Reply, 185 | hemera: h, 186 | } 187 | 188 | oReplyPtr := reflect.ValueOf(reply) 189 | cbValue := reflect.ValueOf(p.Payload) 190 | 191 | // Get "Value" of the reply callback for the reflection Call 192 | 193 | oPtr = reflect.ValueOf(oi) 194 | 195 | // array of arguments for the callback handler 196 | var oV []reflect.Value 197 | 198 | if numArgs == 2 { 199 | oV = []reflect.Value{oPtr, oReplyPtr} 200 | } else { 201 | oV = []reflect.Value{oPtr, oReplyPtr, oContextPtr} 202 | } 203 | 204 | cbValue.Call(oV) 205 | } else { 206 | log.Fatal(NewErrorSimple("act: pattern could not be found")) 207 | } 208 | } 209 | 210 | // Act is a method to send a message to a NATS subscriber which the specific topic 211 | func (h *Hemera) Act(args ...interface{}) *Context { 212 | context := &Context{} 213 | 214 | if len(args) < 2 { 215 | context.Error = NewErrorSimple("act: invalid count of arguments") 216 | return context 217 | } 218 | 219 | p := args[0] 220 | out := args[1] 221 | 222 | var ctx *Context 223 | 224 | if len(args) == 3 { 225 | ctx = args[2].(*Context) 226 | } 227 | 228 | s := structs.New(p) 229 | topicField := s.Field("Topic") 230 | 231 | if topicField.IsZero() { 232 | context.Error = NewErrorSimple("act: topic is required") 233 | return context 234 | } 235 | 236 | topic, ok := topicField.Value().(string) 237 | 238 | if !ok { 239 | context.Error = NewErrorSimple("act: topic must be from type string") 240 | return context 241 | } 242 | 243 | var metaField Meta 244 | var delegateField Delegate 245 | 246 | if ctx == nil { 247 | if field, ok := s.FieldOk("Meta"); ok { 248 | metaField = field.Value().(Meta) 249 | } 250 | 251 | if field, ok := s.FieldOk("Delegate"); ok { 252 | delegateField = field.Value().(Delegate) 253 | } 254 | } else { 255 | metaField = ctx.Meta 256 | delegateField = ctx.Delegate 257 | } 258 | 259 | request := packet{ 260 | Pattern: CleanPattern(s), 261 | Meta: metaField, 262 | Delegate: delegateField, 263 | Trace: Trace{ 264 | TraceID: nuid.Next(), 265 | }, 266 | Request: request{ 267 | ID: nuid.Next(), 268 | RequestType: RequestType, 269 | }, 270 | } 271 | 272 | data, err := jsoniter.Marshal(&request) 273 | 274 | m, err := h.Conn.Request(topic, data, h.Opts.Timeout*time.Millisecond) 275 | 276 | if err != nil { 277 | log.Fatal(err) 278 | context.Error = err 279 | return context 280 | } 281 | 282 | pack := packet{} 283 | mErr := jsoniter.Unmarshal(m.Data, &pack) 284 | 285 | if mErr != nil { 286 | log.Fatal(mErr) 287 | context.Error = mErr 288 | return context 289 | } 290 | 291 | errResMap := mapstructure.Decode(pack.Result, out) 292 | 293 | if errResMap != nil { 294 | panic(errResMap) 295 | } 296 | 297 | responseError := pack.Error 298 | 299 | // create container for error 300 | errorMsg := &Error{} 301 | 302 | if responseError != nil { 303 | // Decode error map to struct 304 | errErrMap := mapstructure.Decode(responseError, errorMsg) 305 | 306 | if errErrMap != nil { 307 | panic(errErrMap) 308 | } 309 | } 310 | 311 | context.Trace = pack.Trace 312 | context.Meta = pack.Meta 313 | context.Delegate = pack.Delegate 314 | 315 | return context 316 | } 317 | 318 | // Dissect the cb Handler's signature 319 | func ArgInfo(cb Handler) ([]reflect.Type, int) { 320 | cbType := reflect.TypeOf(cb) 321 | 322 | if cbType.Kind() != reflect.Func { 323 | panic("hemera: handler needs to be a func") 324 | } 325 | 326 | numArgs := cbType.NumIn() 327 | argTypes := []reflect.Type{} 328 | 329 | for i := 0; i < numArgs; i++ { 330 | argTypes = append(argTypes, cbType.In(i)) 331 | } 332 | 333 | return argTypes, numArgs 334 | } 335 | 336 | func CleanPattern(s *structs.Struct) interface{} { 337 | var pattern = make(map[string]interface{}) 338 | 339 | for _, f := range s.Fields() { 340 | if f.IsExported() { 341 | switch f.Value().(type) { 342 | case Meta: 343 | case Delegate: 344 | default: 345 | pattern[f.Name()] = f.Value() 346 | } 347 | } 348 | 349 | } 350 | 351 | return pattern 352 | } 353 | -------------------------------------------------------------------------------- /router/router_test.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func CreateRouter(t *testing.T) { 10 | assert := assert.New(t) 11 | 12 | hr := NewRouter(true) 13 | 14 | assert.NotEqual(hr, nil, "they should not nil") 15 | 16 | } 17 | 18 | type DynPattern struct { 19 | Topic string 20 | Cmd string 21 | A string 22 | B string 23 | C string 24 | D string 25 | E string 26 | F string 27 | G string 28 | H string 29 | I string 30 | J string 31 | } 32 | 33 | type TestIntPattern struct { 34 | Topic string 35 | Cmd string 36 | A int 37 | B int 38 | } 39 | 40 | var hrouterDepth = NewRouter(true) 41 | var hrouterInsertion = NewRouter(false) 42 | 43 | /** 44 | * Pattern weight order 45 | */ 46 | 47 | func TestAddPatternDepth(t *testing.T) { 48 | assert := assert.New(t) 49 | 50 | hr := NewRouter(true) 51 | hr.Add(DynPattern{Topic: "math", Cmd: "add"}, "dede") 52 | hr.Add(DynPattern{Topic: "math", Cmd: "add"}, "deded") 53 | hr.Add(DynPattern{Topic: "payment", Cmd: "add"}, "deded") 54 | hr.Add(DynPattern{Topic: "payment", Cmd: "add", A: "2"}, "deded") 55 | 56 | assert.Equal(len(hr.List()), 4, "Should contain 4 pattern") 57 | 58 | } 59 | 60 | func TestMatchedLookupDepth(t *testing.T) { 61 | assert := assert.New(t) 62 | 63 | hr := NewRouter(true) 64 | hr.Add(DynPattern{Topic: "math"}, "test") 65 | hr.Add(DynPattern{Topic: "payment"}, "test2") 66 | hr.Add(DynPattern{Topic: "math", Cmd: "add"}, "test3") 67 | hr.Add(DynPattern{Topic: "math", Cmd: "add", A: "1"}, "test4") 68 | hr.Add(DynPattern{Topic: "math", Cmd: "add", A: "1", B: "1"}, "test5") 69 | 70 | p := hr.Lookup(DynPattern{Topic: "math", Cmd: "add"}) 71 | 72 | assert.Equal(p.Payload, "test3", "Should be `test3`") 73 | 74 | } 75 | 76 | func TestMatchedLookupWhenEqualWeightDepth(t *testing.T) { 77 | assert := assert.New(t) 78 | 79 | hr := NewRouter(true) 80 | hr.Add(DynPattern{Topic: "math"}, "test") 81 | 82 | p := hr.Lookup(DynPattern{Topic: "math"}) 83 | 84 | assert.Equal(p.Payload, "test", "Should be `test`") 85 | 86 | } 87 | 88 | func TestDepthSupport(t *testing.T) { 89 | assert := assert.New(t) 90 | 91 | hr := NewRouter(true) 92 | hr.Add(DynPattern{Topic: "math"}, "test") 93 | hr.Add(DynPattern{Topic: "math", Cmd: "add"}, "test1") 94 | 95 | p := hr.Lookup(DynPattern{Topic: "math", Cmd: "add"}) 96 | 97 | assert.Equal(p.Payload, "test1", "Should be `test1`") 98 | 99 | } 100 | 101 | func TestOrderSupport(t *testing.T) { 102 | assert := assert.New(t) 103 | 104 | hr := NewRouter(false) 105 | hr.Add(DynPattern{Topic: "math"}, "test") 106 | hr.Add(DynPattern{Topic: "math", Cmd: "add"}, "test1") 107 | 108 | p := hr.Lookup(DynPattern{Topic: "math", Cmd: "add"}) 109 | 110 | assert.Equal(p.Payload, "test", "Should be `test`") 111 | 112 | } 113 | 114 | func TestDepthPreserveInsertionOrder(t *testing.T) { 115 | assert := assert.New(t) 116 | 117 | o1 := DynPattern{Topic: "math"} 118 | o2 := DynPattern{Topic: "math"} 119 | 120 | hr := NewRouter(true) 121 | hr.Add(o1, "test1") 122 | hr.Add(o2, "test2") 123 | 124 | p := hr.Lookup(TestIntPattern{Topic: "math"}) 125 | 126 | assert.Equal(p.Payload, "test1", "Should be `test1`") 127 | 128 | } 129 | 130 | func TestMatchedLookupNotExistKeyDepth(t *testing.T) { 131 | assert := assert.New(t) 132 | 133 | hr := NewRouter(true) 134 | hr.Add(DynPattern{Topic: "math"}, "test") 135 | hr.Add(DynPattern{Topic: "math", Cmd: "add"}, "test1") 136 | 137 | p := hr.Lookup(TestIntPattern{Topic: "math", Cmd: "add", A: 1, B: 1}) 138 | 139 | assert.Equal(p.Payload, "test1", "Should be `test1`") 140 | 141 | } 142 | 143 | func TestMatchedLookupLastDepth(t *testing.T) { 144 | assert := assert.New(t) 145 | 146 | hr := NewRouter(true) 147 | hr.Add(DynPattern{Topic: "math"}, "test") 148 | hr.Add(DynPattern{Topic: "math", Cmd: "add", A: "1"}, "test1") 149 | 150 | p := hr.Lookup(DynPattern{Topic: "math", Cmd: "add", A: "1"}) 151 | 152 | assert.Equal(p.Payload, "test1", "Should be `test`") 153 | 154 | } 155 | 156 | func TestMatchedLookupWhenSubsetDepth(t *testing.T) { 157 | assert := assert.New(t) 158 | 159 | hr := NewRouter(true) 160 | hr.Add(DynPattern{Topic: "math"}, "test") 161 | hr.Add(DynPattern{Topic: "payment"}, "test2") 162 | hr.Add(DynPattern{Topic: "math", Cmd: "add", A: "1"}, "test4") 163 | hr.Add(DynPattern{Topic: "math", Cmd: "add", A: "1", B: "1"}, "test5") 164 | 165 | p := hr.Lookup(DynPattern{Topic: "math", Cmd: "add"}) 166 | 167 | assert.Equal(p.Payload, "test", "Should be `test`") 168 | 169 | } 170 | 171 | func TestUnMatchedLookupNoPartialMatchSupportDepth(t *testing.T) { 172 | assert := assert.New(t) 173 | 174 | hr := NewRouter(true) 175 | hr.Add(DynPattern{Topic: "math", Cmd: "add", A: "1"}, "test4") 176 | 177 | p := hr.Lookup(DynPattern{Topic: "math", Cmd: "add"}) 178 | 179 | assert.Empty(p, "Pattern not found", "Should pattern not found") 180 | 181 | } 182 | 183 | func TestUnMatchedLookupWhenTreeEmptyDepth(t *testing.T) { 184 | assert := assert.New(t) 185 | 186 | hr := NewRouter(true) 187 | 188 | p := hr.Lookup(DynPattern{Topic: "math", Cmd: "add222"}) 189 | 190 | assert.Empty(p, "Pattern not found", "Should pattern not found") 191 | 192 | } 193 | 194 | /** 195 | * Depth 196 | */ 197 | 198 | func BenchmarkLookupWeightDepth7(b *testing.B) { 199 | 200 | for n := 0; n < b.N; n++ { 201 | hrouterDepth.Lookup(DynPattern{Topic: "math", Cmd: "add", A: "1", B: "2", C: "foo", D: "11", E: "d23"}) 202 | } 203 | 204 | } 205 | 206 | func BenchmarkLookupWeightDepth6(b *testing.B) { 207 | 208 | for n := 0; n < b.N; n++ { 209 | hrouterDepth.Lookup(DynPattern{Topic: "math", Cmd: "add", A: "1", B: "2", C: "foo", D: "dedede"}) 210 | } 211 | 212 | } 213 | 214 | func BenchmarkLookupWeightDepth5(b *testing.B) { 215 | 216 | for n := 0; n < b.N; n++ { 217 | hrouterDepth.Lookup(DynPattern{Topic: "math", Cmd: "add", A: "1", B: "2", C: "foo"}) 218 | } 219 | 220 | } 221 | 222 | func BenchmarkLookupWeightDepth4(b *testing.B) { 223 | 224 | for n := 0; n < b.N; n++ { 225 | hrouterDepth.Lookup(DynPattern{Topic: "math", Cmd: "add", A: "1", B: "2"}) 226 | } 227 | 228 | } 229 | 230 | func BenchmarkLookupWeightDepth3(b *testing.B) { 231 | 232 | for n := 0; n < b.N; n++ { 233 | hrouterDepth.Lookup(DynPattern{Topic: "math", Cmd: "add", A: "1"}) 234 | } 235 | 236 | } 237 | 238 | func BenchmarkLookupWeightDepth2(b *testing.B) { 239 | 240 | for n := 0; n < b.N; n++ { 241 | hrouterDepth.Lookup(DynPattern{Topic: "math", Cmd: "add"}) 242 | } 243 | 244 | } 245 | 246 | func BenchmarkLookupWeightDepth1(b *testing.B) { 247 | 248 | for n := 0; n < b.N; n++ { 249 | hrouterDepth.Lookup(DynPattern{Topic: "math"}) 250 | } 251 | 252 | } 253 | 254 | func BenchmarkListDepth10000(b *testing.B) { 255 | 256 | for n := 0; n < b.N; n++ { 257 | hrouterDepth.List() 258 | } 259 | 260 | } 261 | 262 | func BenchmarkAddDepth(b *testing.B) { 263 | 264 | hr := NewRouter(true) 265 | 266 | for n := 0; n < b.N; n++ { 267 | hr.Add(DynPattern{Topic: "math", Cmd: "add", A: "1", B: "1"}, "test") 268 | } 269 | 270 | } 271 | 272 | /** 273 | * Insertion 274 | */ 275 | 276 | func BenchmarkLookupWeightInsertion7(b *testing.B) { 277 | 278 | for n := 0; n < b.N; n++ { 279 | hrouterInsertion.Lookup(DynPattern{Topic: "math", Cmd: "add", A: "1", B: "2", C: "foo", D: "11", E: "d23"}) 280 | } 281 | 282 | } 283 | 284 | func BenchmarkLookupWeightInsertion6(b *testing.B) { 285 | 286 | for n := 0; n < b.N; n++ { 287 | hrouterInsertion.Lookup(DynPattern{Topic: "math", Cmd: "add", A: "1", B: "2", C: "foo", D: "dedede"}) 288 | } 289 | 290 | } 291 | 292 | func BenchmarkLookupWeightInsertion5(b *testing.B) { 293 | 294 | for n := 0; n < b.N; n++ { 295 | hrouterInsertion.Lookup(DynPattern{Topic: "math", Cmd: "add", A: "1", B: "2", C: "foo"}) 296 | } 297 | 298 | } 299 | 300 | func BenchmarkLookupWeightInsertion4(b *testing.B) { 301 | 302 | for n := 0; n < b.N; n++ { 303 | hrouterInsertion.Lookup(DynPattern{Topic: "math", Cmd: "add", A: "1", B: "2"}) 304 | } 305 | 306 | } 307 | 308 | func BenchmarkLookupWeightInsertion3(b *testing.B) { 309 | 310 | for n := 0; n < b.N; n++ { 311 | hrouterInsertion.Lookup(DynPattern{Topic: "math", Cmd: "add", A: "1"}) 312 | } 313 | 314 | } 315 | 316 | func BenchmarkLookupWeightInsertion2(b *testing.B) { 317 | 318 | for n := 0; n < b.N; n++ { 319 | hrouterInsertion.Lookup(DynPattern{Topic: "math", Cmd: "add"}) 320 | } 321 | 322 | } 323 | 324 | func BenchmarkLookupWeightInsertion1(b *testing.B) { 325 | 326 | for n := 0; n < b.N; n++ { 327 | hrouterInsertion.Lookup(DynPattern{Topic: "math"}) 328 | } 329 | 330 | } 331 | 332 | func BenchmarkListInsertion100000(b *testing.B) { 333 | 334 | for n := 0; n < b.N; n++ { 335 | hrouterInsertion.List() 336 | } 337 | 338 | } 339 | 340 | func BenchmarkAddInsertion(b *testing.B) { 341 | 342 | hr := NewRouter(false) 343 | 344 | for n := 0; n < b.N; n++ { 345 | hr.Add(DynPattern{Topic: "math", Cmd: "add", A: "1", B: "1"}, "test") 346 | } 347 | 348 | } 349 | 350 | func init() { 351 | 352 | for n := 0; n < 100; n++ { 353 | hrouterDepth.Add(DynPattern{Topic: "payment"}, "test1") 354 | hrouterDepth.Add(DynPattern{Topic: "math", Cmd: "add"}, "test2") 355 | hrouterDepth.Add(DynPattern{Topic: "math", Cmd: "add", A: "1"}, "test3") 356 | hrouterDepth.Add(DynPattern{Topic: "math", Cmd: "add", A: "1"}, "test3") 357 | hrouterDepth.Add(DynPattern{Topic: "math", Cmd: "add", A: "1", B: "2", C: "foo"}, "test5") 358 | hrouterDepth.Add(DynPattern{Topic: "math", Cmd: "add", A: "1", B: "1"}, "test4") 359 | hrouterDepth.Add(DynPattern{Topic: "math", Cmd: "add", A: "1", B: "2", C: "foo"}, "test5") 360 | hrouterDepth.Add(DynPattern{Topic: "math", Cmd: "add", A: "1", B: "2", C: "foo", D: "dedede"}, "test6") 361 | hrouterDepth.Add(DynPattern{Topic: "math", Cmd: "add", A: "1", B: "2", C: "foo", D: "11", E: "d23"}, "test7") 362 | hrouterDepth.Add(DynPattern{Topic: "order"}, "test1") 363 | } 364 | 365 | for n := 0; n < 100; n++ { 366 | hrouterInsertion.Add(DynPattern{Topic: "payment"}, "test1") 367 | hrouterInsertion.Add(DynPattern{Topic: "math", Cmd: "add"}, "test2") 368 | hrouterInsertion.Add(DynPattern{Topic: "math", Cmd: "add", A: "1"}, "test3") 369 | hrouterInsertion.Add(DynPattern{Topic: "math", Cmd: "add", A: "1"}, "test3") 370 | hrouterInsertion.Add(DynPattern{Topic: "math", Cmd: "add", A: "1", B: "2", C: "foo"}, "test5") 371 | hrouterInsertion.Add(DynPattern{Topic: "math", Cmd: "add", A: "1", B: "1"}, "test4") 372 | hrouterInsertion.Add(DynPattern{Topic: "math", Cmd: "add", A: "1", B: "2", C: "foo"}, "test5") 373 | hrouterInsertion.Add(DynPattern{Topic: "math", Cmd: "add", A: "1", B: "2", C: "foo", D: "dedede"}, "test6") 374 | hrouterInsertion.Add(DynPattern{Topic: "math", Cmd: "add", A: "1", B: "2", C: "foo", D: "11", E: "d23"}, "test7") 375 | hrouterInsertion.Add(DynPattern{Topic: "order"}, "test1") 376 | } 377 | } 378 | --------------------------------------------------------------------------------