├── .gitignore ├── LICENSE ├── README.md ├── config └── service_config.go ├── constants ├── defaults.go ├── event_type.go └── keys.go ├── context ├── id_generator.go └── saga_agent_context.go ├── degorator └── degorator.go ├── errors └── saga_error.go ├── go.mod ├── go.sum ├── log ├── logger.go ├── noop_logger.go └── std_logger.go ├── metadata └── metadata.go ├── middleware ├── extract_saga_ctx_for_gin.go └── extract_saga_ctx_for_http.go ├── processor └── compensation_processor.go ├── saga_grpc ├── GrpcTxEvent.pb.go └── GrpcTxEvent.proto ├── sagatx.go ├── serializer ├── api.go └── gob_serializer.go ├── test ├── old │ └── demo.go ├── sagaend │ └── sagatx_demo.go └── sagatx_demo.go ├── transport └── transport_contractor.go └── utils ├── devutils.go ├── netutils.go └── reflectutils.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 | 14 | vendor 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 简述 2 | 3 | Saga事务方案目前已有较成熟的开源实现[servicecomb-saga](https://github.com/apache/incubator-servicecomb-saga),但servicecomb-saga是采用java语言实现的,对于golang语言的业务项目来说接入servicecomb-saga方案有一些困难,本项目尝试为servicecomb-saga实现一个golang语言的omega,以帮助golang语言的业务项目接入。 4 | 5 | 本程序库参考[servicecomb-saga的omega模块](https://github.com/apache/incubator-servicecomb-saga/tree/master/omega),完全兼容servicecomb-saga java版实现,可与java版互操作。 6 | 7 | ## 使用方法 8 | 9 | 可参考[test示例](./test),下面说明主要步骤 10 | 11 | 假设原有的业务代码如下: 12 | 13 | `test/old/demo.go` 14 | 15 | ```go 16 | package main 17 | 18 | import ( 19 | "fmt" 20 | "time" 21 | "os" 22 | "os/signal" 23 | "syscall" 24 | ) 25 | 26 | var ( 27 | BALANCES map[string]int 28 | ) 29 | 30 | func init() { 31 | initDatas() 32 | } 33 | 34 | func initDatas(){ 35 | BALANCES = make(map[string]int, 0) 36 | BALANCES["foo"] = 500 37 | BALANCES["bar"] = 500 38 | } 39 | 40 | func TransferMoney() error { 41 | err := TransferOut("foo", 100) 42 | if err != nil { 43 | return err 44 | } 45 | err = TransferIn("bar", 100) 46 | if err != nil { 47 | return err 48 | } 49 | return nil 50 | } 51 | 52 | func TransferOut(from string, amount int) error { 53 | oldAmount, _ := BALANCES[from] 54 | BALANCES[from] = oldAmount - amount 55 | return nil 56 | } 57 | 58 | func TransferIn(to string, amount int) error { 59 | oldAmount, _ := BALANCES[to] 60 | BALANCES[to] = oldAmount + amount 61 | return nil 62 | } 63 | 64 | func main() { 65 | TransferMoney() 66 | stopped := false 67 | go func() { 68 | s := make(chan os.Signal) 69 | signal.Notify(s, syscall.SIGINT, syscall.SIGTERM) 70 | <-s 71 | stopped = true 72 | }() 73 | for !stopped { 74 | fmt.Println(BALANCES["foo"], BALANCES["bar"]) 75 | time.Sleep(time.Second * 3) 76 | } 77 | } 78 | ``` 79 | 80 | 上面的代码比较简单,就是完成一个基本的转帐,将钱从一个帐户转到另一个帐户。 81 | 82 | 只需要根据以下的步骤,进行简单的改造即可接入分布式事务支持,最终改造后的代码见[test/sagatx_demo.go](./test/sagatx_demo.go)。 83 | 84 | ### 初始化SagaAgent 85 | 86 | 在程序入口处初始化SagaAgent,代码如下: 87 | 88 | ```go 89 | func main(){ 90 | ...... 91 | saga.InitSagaAgent("saga-go-demo", "10.12.142.216:30571", nil) 92 | ...... 93 | } 94 | ``` 95 | 96 | ### 构造SagaStart、Compensable方法 97 | 98 | 由于go语言特性,无法无侵入地进行AOP编程,只能采用Decorator模式代替,因此用Decorator对原来的分布事务入口函数、本地事务函数进行包装,代码如下: 99 | 100 | ```go 101 | var ( 102 | TransferMoneySagaStartDecorated func() error 103 | TransferOutCompensableDecorated func(from string, amount int) error 104 | TransferInCompensableDecorated func(to string, amount int) error 105 | ) 106 | 107 | func init() { 108 | err := saga.DecorateSagaStartMethod(&TransferMoneySagaStartDecorated, TransferMoney, 20) 109 | if err != nil { 110 | panic(err) 111 | } 112 | err = saga.DecorateCompensableMethod(&TransferOutCompensableDecorated, TransferOut, CancelTransferOut, 5) 113 | if err != nil { 114 | panic(err) 115 | } 116 | err = saga.DecorateCompensableMethod(&TransferInCompensableDecorated, TransferIn, CancelTransferIn, 5) 117 | if err != nil { 118 | panic(err) 119 | } 120 | 121 | ...... 122 | } 123 | 124 | ....... 125 | 126 | func CancelTransferOut(from string, amount int) error { 127 | oldAmount, _ := BALANCES[from] 128 | BALANCES[from] = oldAmount + amount 129 | return nil 130 | } 131 | 132 | ...... 133 | 134 | func CancelTransferIn(to string, amount int) error { 135 | oldAmount, _ := BALANCES[to] 136 | BALANCES[to] = oldAmount - amount 137 | return nil 138 | } 139 | ``` 140 | 141 | **注意,每个本地事务函数要提供对应的幂等补偿函数** 142 | 143 | ### 修改对应的包装函数 144 | 145 | 由于go语言特性,无法无侵入地进行AOP编程,需要手动将原来的分布事务入口函数、本地事务函数修改为对应的包装函数,代码如下: 146 | 147 | ```go 148 | func TransferMoney() error { 149 | //err := TransferOut("foo", 100) 150 | err := TransferOutCompensableDecorated("foo", 100) 151 | if err != nil { 152 | return err 153 | } 154 | //err = TransferIn("bar", 100) 155 | err = TransferInCompensableDecorated("bar", 100) 156 | if err != nil { 157 | return err 158 | } 159 | return nil 160 | } 161 | 162 | func main() { 163 | ...... 164 | //TransferMoney() 165 | TransferMoneySagaStartDecorated() 166 | ...... 167 | } 168 | ``` 169 | 170 | ### 传递saga上下文信息 171 | 172 | 如果一个分布式事务涉及到多个业务服务,则需要在业务服务间传递saga上下文信息,这里涉及两个步骤。 173 | 174 | 1. 在接收到HTTP请求处使用middleware接收saga上下文信息,请使用合适的[middleware](./middleware)进行处理。 175 | 176 | 2. 发送HTTP请求到其它业务服务时,使用[sagactx.InjectIntoHttpHeaders](./context/saga_agent_context.go)将当前的saga上下文信息织入HTTP请求头中。 177 | 178 | ## TODO 179 | 180 | 1. 支持多alpha负载均衡 181 | 182 | 183 | ## License 184 | Licensed under an [Apache 2.0 license](LICENSE). -------------------------------------------------------------------------------- /config/service_config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "strings" 5 | "github.com/jeremyxu2010/matrix-saga-go/utils" 6 | ) 7 | 8 | func NewServiceConfig(serviceName string) *ServiceConfig{ 9 | hostAddress, err := utils.GetFirstNotLoopbackIPv4Address() 10 | if err != nil { 11 | panic(err) 12 | } 13 | instanceId := strings.Join([]string{serviceName, hostAddress}, "-") 14 | return &ServiceConfig{ 15 | ServiceName: serviceName, 16 | InstanceId: instanceId, 17 | } 18 | } 19 | 20 | type ServiceConfig struct{ 21 | ServiceName string 22 | InstanceId string 23 | } 24 | -------------------------------------------------------------------------------- /constants/defaults.go: -------------------------------------------------------------------------------- 1 | package constants 2 | 3 | import "time" 4 | 5 | const ( 6 | GRPC_COMMUNICATE_TIMEOUT = time.Second * 5 7 | GRPC_RECONNECT_DELAY = time.Second * 10 8 | 9 | PAYLOADS_MAX_LENGTH = 10240 10 | ) 11 | -------------------------------------------------------------------------------- /constants/event_type.go: -------------------------------------------------------------------------------- 1 | package constants 2 | 3 | const ( 4 | EVENT_NAME_SAGASTARTEDEVENT = "SagaStartedEvent" 5 | EVENT_NAME_TXSTARTEDEVENT = "TxStartedEvent" 6 | EVENT_NAME_TXENDEDEVENT = "TxEndedEvent" 7 | EVENT_NAME_TXABORTEDEVENT = "TxAbortedEvent" 8 | EVENT_NAME_TXCOMPENSATEDEVENT = "TxCompensatedEvent" 9 | EVENT_NAME_SAGAENDEDEVENT = "SagaEndedEvent" 10 | ) 11 | -------------------------------------------------------------------------------- /constants/keys.go: -------------------------------------------------------------------------------- 1 | package constants 2 | 3 | const ( 4 | KEY_FUNCTION_CALL_ARGS = "KEY_FUNCTION_CALL_ARGS" 5 | KEY_FUNCTION_CALL_ERROR = "KEY_FUNCTION_CALL_ERROR" 6 | KEY_PARENT_LOCAL_TX_ID = "KEY_PARENT_LOCAL_TX_ID" 7 | KEY_SAGA_AGENT_CONTEXT = "KEY_SAGA_AGENT_CONTEXT" 8 | 9 | KEY_GLOBAL_TX_ID_KEY = "X-Pack-Global-Transaction-Id" 10 | KEY_LOCAL_TX_ID_KEY = "X-Pack-Local-Transaction-Id" 11 | ) 12 | -------------------------------------------------------------------------------- /context/id_generator.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import "github.com/satori/go.uuid" 4 | 5 | type IdGenerator struct{ 6 | } 7 | 8 | func (g *IdGenerator)NextId()string{ 9 | return uuid.NewV4().String() 10 | } 11 | 12 | var idGenerator *IdGenerator 13 | 14 | func init() { 15 | idGenerator = &IdGenerator{} 16 | } 17 | 18 | -------------------------------------------------------------------------------- /context/saga_agent_context.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | import ( 4 | "errors" 5 | "github.com/cosmos72/gls" 6 | "github.com/jeremyxu2010/matrix-saga-go/constants" 7 | "net/http" 8 | ) 9 | 10 | type SagaAgentContext struct { 11 | GlobalTxId string 12 | LocalTxId string 13 | } 14 | 15 | func NewSagaAgentContext()*SagaAgentContext{ 16 | return &SagaAgentContext{} 17 | } 18 | 19 | func (c *SagaAgentContext) Initialize() { 20 | c.GlobalTxId = idGenerator.NextId() 21 | c.LocalTxId = c.GlobalTxId 22 | SetSagaAgentContext(c) 23 | } 24 | 25 | func SetSagaAgentContext(c *SagaAgentContext) { 26 | gls.Set(constants.KEY_SAGA_AGENT_CONTEXT, c) 27 | } 28 | 29 | func ClearSagaAgentContext(){ 30 | gls.Del(constants.KEY_SAGA_AGENT_CONTEXT) 31 | } 32 | 33 | func (c *SagaAgentContext) NewLocalTxId() { 34 | c.LocalTxId = idGenerator.NextId() 35 | } 36 | 37 | func GetSagaAgentContext()(*SagaAgentContext, error){ 38 | if v, ok := gls.Get(constants.KEY_SAGA_AGENT_CONTEXT); ok { 39 | if sagaAgentContext, ok := v.(*SagaAgentContext); ok { 40 | return sagaAgentContext, nil 41 | } else { 42 | return nil, errors.New("Can not found SagaAgentContext") 43 | } 44 | } else { 45 | return nil, errors.New("Can not found SagaAgentContext") 46 | } 47 | } 48 | 49 | func ExtractFromHttpHeaders(headers http.Header){ 50 | if _, ok := gls.Get(constants.KEY_SAGA_AGENT_CONTEXT); !ok { 51 | if len(headers.Get(constants.KEY_GLOBAL_TX_ID_KEY)) > 0 { 52 | c := NewSagaAgentContext() 53 | c.GlobalTxId = headers.Get(constants.KEY_GLOBAL_TX_ID_KEY) 54 | c.LocalTxId = headers.Get(constants.KEY_LOCAL_TX_ID_KEY) 55 | SetSagaAgentContext(c) 56 | } 57 | } 58 | } 59 | 60 | func InjectIntoHttpHeaders(headers http.Header){ 61 | c, _ := GetSagaAgentContext() 62 | if c != nil { 63 | headers.Set(constants.KEY_GLOBAL_TX_ID_KEY, c.GlobalTxId) 64 | headers.Set(constants.KEY_GLOBAL_TX_ID_KEY, c.LocalTxId) 65 | } 66 | } 67 | 68 | func MustGetSagaAgentContext()*SagaAgentContext{ 69 | sagaAgentContext, err := GetSagaAgentContext() 70 | if err != nil { 71 | panic(err) 72 | } 73 | return sagaAgentContext 74 | } 75 | -------------------------------------------------------------------------------- /degorator/degorator.go: -------------------------------------------------------------------------------- 1 | package degorator 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "errors" 7 | "context" 8 | "github.com/jeremyxu2010/matrix-saga-go/constants" 9 | "github.com/jeremyxu2010/matrix-saga-go/metadata" 10 | ) 11 | 12 | var CONTEXT_TYPE_NAME = "context.Context" 13 | var ERROR_TYPE_NAME = "error" 14 | 15 | // Decorate injects two functions(injectedBefore & injectedAfter) into the target function. 16 | // The argument decorated is the function after decoration. 17 | // The argument target is the function to be decorated. 18 | // The argument before is the function to be injected before the target function. 19 | // The argument after is the function to be injected after the target function. 20 | func Decorate(decorated interface{}, target interface{}, before interface{}, after interface{}) (err error) { 21 | var targetFunc reflect.Value 22 | var decoratedFunc reflect.Value 23 | var beforeFunc reflect.Value 24 | var afterFunc reflect.Value 25 | 26 | decoratedFunc, err = checkFPTR(decorated) 27 | if err != nil { 28 | return 29 | } 30 | 31 | targetFunc = reflect.ValueOf(target) 32 | if targetFunc.Kind() != reflect.Func { 33 | err = fmt.Errorf("Input target para is not a function.") 34 | return 35 | } 36 | 37 | beforeFunc, afterFunc, err = checkInjection(targetFunc.Type(), before, after) 38 | if err != nil { 39 | return 40 | } 41 | 42 | decoratedFunc.Set(reflect.MakeFunc(targetFunc.Type(), func(in []reflect.Value) (out []reflect.Value) { 43 | ctx := context.Background() 44 | ctx = metadata.NewContext(ctx, make(metadata.Metadata)) 45 | if targetFunc.Type().IsVariadic() { 46 | if before != nil { 47 | if m, ok := metadata.FromContext(ctx); ok { 48 | m[constants.KEY_FUNCTION_CALL_ARGS] = in 49 | } 50 | beforeOut := beforeFunc.CallSlice([]reflect.Value{reflect.ValueOf(ctx)}) 51 | if !beforeOut[0].IsNil() { 52 | return 53 | } 54 | } 55 | out, err = safeCallSlice(targetFunc, in) 56 | if after != nil { 57 | if m, ok := metadata.FromContext(ctx); ok { 58 | m[constants.KEY_FUNCTION_CALL_ERROR] = err 59 | } 60 | afterOut := afterFunc.CallSlice([]reflect.Value{reflect.ValueOf(ctx)}) 61 | if !afterOut[0].IsNil() { 62 | return 63 | } 64 | } 65 | } else { 66 | if before != nil { 67 | if m, ok := metadata.FromContext(ctx); ok { 68 | m[constants.KEY_FUNCTION_CALL_ARGS] = in 69 | } 70 | beforeOut := beforeFunc.Call([]reflect.Value{reflect.ValueOf(ctx)}) 71 | if !beforeOut[0].IsNil() { 72 | return 73 | } 74 | } 75 | out, err = safeCall(targetFunc, in) 76 | if after != nil { 77 | if m, ok := metadata.FromContext(ctx); ok { 78 | m[constants.KEY_FUNCTION_CALL_ERROR] = err 79 | } 80 | afterOut := afterFunc.Call([]reflect.Value{reflect.ValueOf(ctx)}) 81 | if !afterOut[0].IsNil() { 82 | return 83 | } 84 | } 85 | } 86 | return 87 | })) 88 | return 89 | } 90 | 91 | func safeCallSlice(targetFunc reflect.Value, in []reflect.Value) (out []reflect.Value, err error) { 92 | defer func() { 93 | cause := recover() 94 | switch cause.(type) { 95 | case error: 96 | err = cause.(error) 97 | case string: 98 | err = errors.New(cause.(string)) 99 | } 100 | }() 101 | out = targetFunc.CallSlice(in) 102 | if len(out) > 0 && (out[len(out) - 1].Type().String() == ERROR_TYPE_NAME) { 103 | if out[len(out) - 1].Interface() != nil { 104 | err = out[len(out)-1].Interface().(error) 105 | } 106 | } 107 | return 108 | } 109 | 110 | func safeCall(targetFunc reflect.Value, in []reflect.Value) (out []reflect.Value, err error) { 111 | defer func() { 112 | cause := recover() 113 | switch cause.(type) { 114 | case error: 115 | err = cause.(error) 116 | case string: 117 | err = errors.New(cause.(string)) 118 | } 119 | }() 120 | out = targetFunc.Call(in) 121 | if len(out) > 0 && (out[len(out) - 1].Type().String() == ERROR_TYPE_NAME) { 122 | if out[len(out) - 1].Interface() != nil { 123 | err = out[len(out) - 1].Interface().(error) 124 | } 125 | } 126 | return 127 | } 128 | 129 | func checkFPTR(fptr interface{}) (function reflect.Value, err error) { 130 | if fptr == nil { 131 | err = fmt.Errorf("Input para is nil.") 132 | return 133 | } 134 | if reflect.TypeOf(fptr).Kind() != reflect.Ptr { 135 | err = fmt.Errorf("Input para is not a pointer.") 136 | return 137 | } 138 | function = reflect.ValueOf(fptr).Elem() 139 | if function.Kind() != reflect.Func { 140 | err = fmt.Errorf("Input para is not a pointer to a function.") 141 | return 142 | } 143 | return 144 | } 145 | 146 | func checkInjection(targetType reflect.Type, before interface{}, after interface{}) (beforeFunc reflect.Value, afterFunc reflect.Value, err error) { 147 | if before != nil { 148 | beforeFunc = reflect.ValueOf(before) 149 | if beforeFunc.Kind() != reflect.Func { 150 | err = fmt.Errorf("Only a function can be injected before.") 151 | return 152 | } 153 | if beforeFunc.Type().NumIn() != 1 { 154 | err = fmt.Errorf("The input para number of the function injected before must be one.") 155 | return 156 | } 157 | if beforeFunc.Type().In(0).String() != CONTEXT_TYPE_NAME { 158 | err = fmt.Errorf("The input para type of the function injected before must be context.Context.") 159 | return 160 | } 161 | if beforeFunc.Type().NumOut() != 1 { 162 | err = fmt.Errorf("The output para number of the function injected before must be one.") 163 | return 164 | } 165 | if beforeFunc.Type().Out(0).String() != ERROR_TYPE_NAME { 166 | err = fmt.Errorf("The output para type of the function injected before must be error.") 167 | return 168 | } 169 | } 170 | if after != nil { 171 | afterFunc = reflect.ValueOf(after) 172 | if afterFunc.Kind() != reflect.Func { 173 | err = fmt.Errorf("Only a function can be injected after.") 174 | return 175 | } 176 | if afterFunc.Type().NumIn() != 1 { 177 | err = fmt.Errorf("The input para number of the function injected after must be one.") 178 | return 179 | } 180 | if afterFunc.Type().In(0).String() != CONTEXT_TYPE_NAME { 181 | err = fmt.Errorf("The input para types of the function injected after must be context.Context.") 182 | return 183 | } 184 | if afterFunc.Type().NumOut() != 1 { 185 | err = fmt.Errorf("The output para number of the function injected after must be one.") 186 | return 187 | } 188 | if afterFunc.Type().Out(0).String() != ERROR_TYPE_NAME { 189 | err = fmt.Errorf("The output para type of the function injected after must be error.") 190 | return 191 | } 192 | } 193 | return 194 | } 195 | 196 | func checkDecorator(decorator interface{}) (decoFunc reflect.Value, err error) { 197 | decoFunc, err = checkFPTR(decorator) 198 | if err != nil { 199 | return 200 | } 201 | if decoFunc.Type().NumIn() != 1 || decoFunc.Type().NumOut() != 1 { 202 | err = fmt.Errorf("Decorator function must have one input para and one output para.") 203 | return 204 | } 205 | if decoFunc.Type().In(0).Kind() != reflect.Func || decoFunc.Type().Out(0).Kind() != reflect.Func { 206 | err = fmt.Errorf("Decorator function's input para type and output para type must be function type.") 207 | return 208 | } 209 | if decoFunc.Type().In(0).NumIn() != decoFunc.Type().Out(0).NumIn() { 210 | err = fmt.Errorf("Decoratee function and decorated function must have same input para number.") 211 | return 212 | } 213 | for i := 0; i < decoFunc.Type().In(0).NumIn(); i++ { 214 | if decoFunc.Type().In(0).In(i) != decoFunc.Type().Out(0).In(i) { 215 | err = fmt.Errorf("Decoratee function and decorated function must have same input para type.") 216 | return 217 | } 218 | } 219 | if decoFunc.Type().In(0).NumOut() != decoFunc.Type().Out(0).NumOut() { 220 | err = fmt.Errorf("Decoratee function and decorated function must have same output para number.") 221 | return 222 | } 223 | for i := 0; i < decoFunc.Type().In(0).NumOut(); i++ { 224 | if decoFunc.Type().In(0).Out(i) != decoFunc.Type().Out(0).Out(i) { 225 | err = fmt.Errorf("Decoratee function and decorated function must have same output para type.") 226 | return 227 | } 228 | } 229 | return 230 | } 231 | -------------------------------------------------------------------------------- /errors/saga_error.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | type SagaAgentError struct { 4 | msg string 5 | } 6 | 7 | func NewSagaAgentError(msg string)*SagaAgentError { 8 | return &SagaAgentError{ 9 | msg: msg, 10 | } 11 | } 12 | 13 | func (e *SagaAgentError) Error()string{ 14 | return e.msg 15 | } 16 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jeremyxu2010/matrix-saga-go 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/cosmos72/gls v0.0.0-20180519201422-29add83bde4c 7 | github.com/gin-gonic/gin v1.4.0 8 | github.com/golang/protobuf v1.3.2 9 | github.com/kr/pretty v0.1.0 // indirect 10 | github.com/satori/go.uuid v1.2.0 11 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 12 | golang.org/x/sys v0.0.0-20190830142957-1e83adbbebd0 // indirect 13 | golang.org/x/text v0.3.2 // indirect 14 | google.golang.org/appengine v1.4.0 // indirect 15 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 // indirect 16 | google.golang.org/grpc v1.23.0 17 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 4 | github.com/cosmos72/gls v0.0.0-20180519201422-29add83bde4c h1:LGlAEBTJGWdoqZ6yBs3dMV17rHCnOOaqR+G5fL/EH58= 5 | github.com/cosmos72/gls v0.0.0-20180519201422-29add83bde4c/go.mod h1:Kyaux0LusPIdFky+mSBBy5NTUDvTDDVpDhO4mJErSpk= 6 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g= 9 | github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= 10 | github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ= 11 | github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= 12 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 13 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 14 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 15 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 16 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 17 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 18 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 19 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 20 | github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= 21 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 22 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 23 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 24 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 25 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 26 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 27 | github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= 28 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 29 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 30 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 31 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 32 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 33 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 34 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 35 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= 36 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 37 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 38 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 39 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 40 | github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= 41 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 42 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 43 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 44 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 45 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 46 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 47 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 48 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 49 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 50 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 51 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 52 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= 53 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 54 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 55 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 56 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 57 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 58 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 59 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 60 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 61 | golang.org/x/sys v0.0.0-20190830142957-1e83adbbebd0 h1:7z820YPX9pxWR59qM7BE5+fglp4D/mKqAwCvGt11b+8= 62 | golang.org/x/sys v0.0.0-20190830142957-1e83adbbebd0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 63 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 64 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 65 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 66 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 67 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 68 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 69 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 70 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 71 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 72 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 73 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 74 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= 75 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 76 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 77 | google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A= 78 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 79 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 80 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 81 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 82 | gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= 83 | gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= 84 | gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= 85 | gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= 86 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 87 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 88 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 89 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 90 | -------------------------------------------------------------------------------- /log/logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | type Logger interface { 4 | LogWarn(content string) 5 | LogError(content string) 6 | LogInfo(content string) 7 | LogDebug(content string) 8 | LogFatal(content string) 9 | } 10 | -------------------------------------------------------------------------------- /log/noop_logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | type noopLogger struct { 4 | } 5 | 6 | func NewNoopLogger() Logger { 7 | return &noopLogger{} 8 | } 9 | 10 | func (l *noopLogger)LogWarn(content string) { 11 | } 12 | 13 | func (l *noopLogger)LogError(content string){ 14 | } 15 | 16 | func (l *noopLogger)LogInfo(content string){ 17 | } 18 | 19 | func (l *noopLogger)LogDebug(content string){ 20 | } 21 | 22 | func (l *noopLogger)LogFatal(content string){ 23 | } 24 | 25 | 26 | -------------------------------------------------------------------------------- /log/std_logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "log" 5 | "os" 6 | ) 7 | 8 | type stdLogger struct { 9 | logger *log.Logger 10 | } 11 | 12 | func NewStdLogger() Logger { 13 | logger := log.New(os.Stdout, "logger: ", log.Llongfile|log.LstdFlags) 14 | return &stdLogger{ 15 | logger: logger, 16 | } 17 | } 18 | 19 | func (l *stdLogger)LogWarn(content string) { 20 | l.logger.Println(content) 21 | } 22 | 23 | func (l *stdLogger)LogError(content string){ 24 | l.logger.Println(content) 25 | } 26 | 27 | func (l *stdLogger)LogInfo(content string){ 28 | l.logger.Println(content) 29 | } 30 | 31 | func (l *stdLogger)LogDebug(content string){ 32 | l.logger.Println(content) 33 | } 34 | 35 | func (l *stdLogger)LogFatal(content string){ 36 | l.logger.Fatalln(content) 37 | } 38 | 39 | 40 | -------------------------------------------------------------------------------- /metadata/metadata.go: -------------------------------------------------------------------------------- 1 | package metadata 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type metaKey struct{} 8 | 9 | // Metadata is our way of representing request headers internally. 10 | // They're used at the RPC level and translate back and forth 11 | // from Transport headers. 12 | type Metadata map[string]interface{} 13 | 14 | func FromContext(ctx context.Context) (Metadata, bool) { 15 | md, ok := ctx.Value(metaKey{}).(Metadata) 16 | return md, ok 17 | } 18 | 19 | func NewContext(ctx context.Context, md Metadata) context.Context { 20 | // create empty context if ctx is nil 21 | if ctx == nil { 22 | ctx = context.Background() 23 | } 24 | return context.WithValue(ctx, metaKey{}, md) 25 | } 26 | -------------------------------------------------------------------------------- /middleware/extract_saga_ctx_for_gin.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | sagactx "github.com/jeremyxu2010/matrix-saga-go/context" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | func ExtractSagaCtxMiddlewareForGin() gin.HandlerFunc { 9 | return func(c *gin.Context) { 10 | sagactx.ExtractFromHttpHeaders(c.Request.Header) 11 | c.Next() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /middleware/extract_saga_ctx_for_http.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "net/http" 5 | sagactx "github.com/jeremyxu2010/matrix-saga-go/context" 6 | ) 7 | 8 | func ExtractSagaCtxMiddlewareForHttp(handler http.HandlerFunc)http.HandlerFunc{ 9 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 10 | sagactx.ExtractFromHttpHeaders(r.Header) 11 | handler(w, r) 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /processor/compensation_processor.go: -------------------------------------------------------------------------------- 1 | package processor 2 | 3 | import ( 4 | "reflect" 5 | "github.com/jeremyxu2010/matrix-saga-go/log" 6 | "fmt" 7 | "github.com/jeremyxu2010/matrix-saga-go/context" 8 | "github.com/jeremyxu2010/matrix-saga-go/serializer" 9 | ) 10 | 11 | type CompensationProcessor struct { 12 | funcs map[string]interface{} 13 | logger log.Logger 14 | s serializer.Serializer 15 | } 16 | 17 | func NewCompensationProcessor(s serializer.Serializer, logger log.Logger)*CompensationProcessor { 18 | return &CompensationProcessor{ 19 | funcs: make(map[string]interface{}, 0), 20 | logger: logger, 21 | s: s, 22 | } 23 | } 24 | 25 | func (p *CompensationProcessor)RegisterCompensationFunc(fnName string, fn interface{}){ 26 | p.funcs[fnName] = reflect.ValueOf(fn) 27 | } 28 | 29 | func (p *CompensationProcessor) ExecuteCompensate(globalTxId string, localTxId string, compensationMethod string, payloads []byte) { 30 | args, err := p.s.Unserialize(payloads) 31 | if err != nil { 32 | p.logger.LogError(fmt.Sprintf("%v", err)) 33 | return 34 | } 35 | if v, ok := p.funcs[compensationMethod]; ok { 36 | if targetFunc, ok := v.(reflect.Value); ok { 37 | sagaAgentCtx, _ := context.GetSagaAgentContext() 38 | if sagaAgentCtx == nil { 39 | sagaAgentCtx = context.NewSagaAgentContext() 40 | sagaAgentCtx.GlobalTxId = globalTxId 41 | sagaAgentCtx.LocalTxId = localTxId 42 | context.SetSagaAgentContext(sagaAgentCtx) 43 | defer func() { 44 | context.ClearSagaAgentContext() 45 | }() 46 | } else { 47 | oldGlobalTxId := sagaAgentCtx.GlobalTxId 48 | oldLocalTxId := sagaAgentCtx.LocalTxId 49 | defer func() { 50 | sagaAgentCtx.GlobalTxId = oldGlobalTxId 51 | sagaAgentCtx.LocalTxId = oldLocalTxId 52 | }() 53 | sagaAgentCtx.GlobalTxId = globalTxId 54 | sagaAgentCtx.LocalTxId = localTxId 55 | } 56 | if targetFunc.Type().IsVariadic() { 57 | targetFunc.CallSlice(args) 58 | } else { 59 | targetFunc.Call(args) 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /saga_grpc/GrpcTxEvent.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: GrpcTxEvent.proto 3 | 4 | package saga_grpc 5 | 6 | import proto "github.com/golang/protobuf/proto" 7 | import fmt "fmt" 8 | import math "math" 9 | 10 | import ( 11 | context "golang.org/x/net/context" 12 | grpc "google.golang.org/grpc" 13 | ) 14 | 15 | // Reference imports to suppress errors if they are not otherwise used. 16 | var _ = proto.Marshal 17 | var _ = fmt.Errorf 18 | var _ = math.Inf 19 | 20 | // This is a compile-time assertion to ensure that this generated file 21 | // is compatible with the proto package it is being compiled against. 22 | // A compilation error at this line likely means your copy of the 23 | // proto package needs to be updated. 24 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 25 | 26 | type GrpcServiceConfig struct { 27 | ServiceName string `protobuf:"bytes,1,opt,name=serviceName,proto3" json:"serviceName,omitempty"` 28 | InstanceId string `protobuf:"bytes,2,opt,name=instanceId,proto3" json:"instanceId,omitempty"` 29 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 30 | XXX_unrecognized []byte `json:"-"` 31 | XXX_sizecache int32 `json:"-"` 32 | } 33 | 34 | func (m *GrpcServiceConfig) Reset() { *m = GrpcServiceConfig{} } 35 | func (m *GrpcServiceConfig) String() string { return proto.CompactTextString(m) } 36 | func (*GrpcServiceConfig) ProtoMessage() {} 37 | func (*GrpcServiceConfig) Descriptor() ([]byte, []int) { 38 | return fileDescriptor_GrpcTxEvent_48a5d9a498bff5ac, []int{0} 39 | } 40 | func (m *GrpcServiceConfig) XXX_Unmarshal(b []byte) error { 41 | return xxx_messageInfo_GrpcServiceConfig.Unmarshal(m, b) 42 | } 43 | func (m *GrpcServiceConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 44 | return xxx_messageInfo_GrpcServiceConfig.Marshal(b, m, deterministic) 45 | } 46 | func (dst *GrpcServiceConfig) XXX_Merge(src proto.Message) { 47 | xxx_messageInfo_GrpcServiceConfig.Merge(dst, src) 48 | } 49 | func (m *GrpcServiceConfig) XXX_Size() int { 50 | return xxx_messageInfo_GrpcServiceConfig.Size(m) 51 | } 52 | func (m *GrpcServiceConfig) XXX_DiscardUnknown() { 53 | xxx_messageInfo_GrpcServiceConfig.DiscardUnknown(m) 54 | } 55 | 56 | var xxx_messageInfo_GrpcServiceConfig proto.InternalMessageInfo 57 | 58 | func (m *GrpcServiceConfig) GetServiceName() string { 59 | if m != nil { 60 | return m.ServiceName 61 | } 62 | return "" 63 | } 64 | 65 | func (m *GrpcServiceConfig) GetInstanceId() string { 66 | if m != nil { 67 | return m.InstanceId 68 | } 69 | return "" 70 | } 71 | 72 | type GrpcAck struct { 73 | Aborted bool `protobuf:"varint,1,opt,name=aborted,proto3" json:"aborted,omitempty"` 74 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 75 | XXX_unrecognized []byte `json:"-"` 76 | XXX_sizecache int32 `json:"-"` 77 | } 78 | 79 | func (m *GrpcAck) Reset() { *m = GrpcAck{} } 80 | func (m *GrpcAck) String() string { return proto.CompactTextString(m) } 81 | func (*GrpcAck) ProtoMessage() {} 82 | func (*GrpcAck) Descriptor() ([]byte, []int) { 83 | return fileDescriptor_GrpcTxEvent_48a5d9a498bff5ac, []int{1} 84 | } 85 | func (m *GrpcAck) XXX_Unmarshal(b []byte) error { 86 | return xxx_messageInfo_GrpcAck.Unmarshal(m, b) 87 | } 88 | func (m *GrpcAck) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 89 | return xxx_messageInfo_GrpcAck.Marshal(b, m, deterministic) 90 | } 91 | func (dst *GrpcAck) XXX_Merge(src proto.Message) { 92 | xxx_messageInfo_GrpcAck.Merge(dst, src) 93 | } 94 | func (m *GrpcAck) XXX_Size() int { 95 | return xxx_messageInfo_GrpcAck.Size(m) 96 | } 97 | func (m *GrpcAck) XXX_DiscardUnknown() { 98 | xxx_messageInfo_GrpcAck.DiscardUnknown(m) 99 | } 100 | 101 | var xxx_messageInfo_GrpcAck proto.InternalMessageInfo 102 | 103 | func (m *GrpcAck) GetAborted() bool { 104 | if m != nil { 105 | return m.Aborted 106 | } 107 | return false 108 | } 109 | 110 | type GrpcTxEvent struct { 111 | Timestamp int64 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` 112 | GlobalTxId string `protobuf:"bytes,2,opt,name=globalTxId,proto3" json:"globalTxId,omitempty"` 113 | LocalTxId string `protobuf:"bytes,3,opt,name=localTxId,proto3" json:"localTxId,omitempty"` 114 | ParentTxId string `protobuf:"bytes,4,opt,name=parentTxId,proto3" json:"parentTxId,omitempty"` 115 | Type string `protobuf:"bytes,5,opt,name=type,proto3" json:"type,omitempty"` 116 | CompensationMethod string `protobuf:"bytes,6,opt,name=compensationMethod,proto3" json:"compensationMethod,omitempty"` 117 | Payloads []byte `protobuf:"bytes,7,opt,name=payloads,proto3" json:"payloads,omitempty"` 118 | ServiceName string `protobuf:"bytes,8,opt,name=serviceName,proto3" json:"serviceName,omitempty"` 119 | InstanceId string `protobuf:"bytes,9,opt,name=instanceId,proto3" json:"instanceId,omitempty"` 120 | Timeout int32 `protobuf:"varint,10,opt,name=timeout,proto3" json:"timeout,omitempty"` 121 | Retries int32 `protobuf:"varint,11,opt,name=retries,proto3" json:"retries,omitempty"` 122 | RetryMethod string `protobuf:"bytes,12,opt,name=retryMethod,proto3" json:"retryMethod,omitempty"` 123 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 124 | XXX_unrecognized []byte `json:"-"` 125 | XXX_sizecache int32 `json:"-"` 126 | } 127 | 128 | func (m *GrpcTxEvent) Reset() { *m = GrpcTxEvent{} } 129 | func (m *GrpcTxEvent) String() string { return proto.CompactTextString(m) } 130 | func (*GrpcTxEvent) ProtoMessage() {} 131 | func (*GrpcTxEvent) Descriptor() ([]byte, []int) { 132 | return fileDescriptor_GrpcTxEvent_48a5d9a498bff5ac, []int{2} 133 | } 134 | func (m *GrpcTxEvent) XXX_Unmarshal(b []byte) error { 135 | return xxx_messageInfo_GrpcTxEvent.Unmarshal(m, b) 136 | } 137 | func (m *GrpcTxEvent) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 138 | return xxx_messageInfo_GrpcTxEvent.Marshal(b, m, deterministic) 139 | } 140 | func (dst *GrpcTxEvent) XXX_Merge(src proto.Message) { 141 | xxx_messageInfo_GrpcTxEvent.Merge(dst, src) 142 | } 143 | func (m *GrpcTxEvent) XXX_Size() int { 144 | return xxx_messageInfo_GrpcTxEvent.Size(m) 145 | } 146 | func (m *GrpcTxEvent) XXX_DiscardUnknown() { 147 | xxx_messageInfo_GrpcTxEvent.DiscardUnknown(m) 148 | } 149 | 150 | var xxx_messageInfo_GrpcTxEvent proto.InternalMessageInfo 151 | 152 | func (m *GrpcTxEvent) GetTimestamp() int64 { 153 | if m != nil { 154 | return m.Timestamp 155 | } 156 | return 0 157 | } 158 | 159 | func (m *GrpcTxEvent) GetGlobalTxId() string { 160 | if m != nil { 161 | return m.GlobalTxId 162 | } 163 | return "" 164 | } 165 | 166 | func (m *GrpcTxEvent) GetLocalTxId() string { 167 | if m != nil { 168 | return m.LocalTxId 169 | } 170 | return "" 171 | } 172 | 173 | func (m *GrpcTxEvent) GetParentTxId() string { 174 | if m != nil { 175 | return m.ParentTxId 176 | } 177 | return "" 178 | } 179 | 180 | func (m *GrpcTxEvent) GetType() string { 181 | if m != nil { 182 | return m.Type 183 | } 184 | return "" 185 | } 186 | 187 | func (m *GrpcTxEvent) GetCompensationMethod() string { 188 | if m != nil { 189 | return m.CompensationMethod 190 | } 191 | return "" 192 | } 193 | 194 | func (m *GrpcTxEvent) GetPayloads() []byte { 195 | if m != nil { 196 | return m.Payloads 197 | } 198 | return nil 199 | } 200 | 201 | func (m *GrpcTxEvent) GetServiceName() string { 202 | if m != nil { 203 | return m.ServiceName 204 | } 205 | return "" 206 | } 207 | 208 | func (m *GrpcTxEvent) GetInstanceId() string { 209 | if m != nil { 210 | return m.InstanceId 211 | } 212 | return "" 213 | } 214 | 215 | func (m *GrpcTxEvent) GetTimeout() int32 { 216 | if m != nil { 217 | return m.Timeout 218 | } 219 | return 0 220 | } 221 | 222 | func (m *GrpcTxEvent) GetRetries() int32 { 223 | if m != nil { 224 | return m.Retries 225 | } 226 | return 0 227 | } 228 | 229 | func (m *GrpcTxEvent) GetRetryMethod() string { 230 | if m != nil { 231 | return m.RetryMethod 232 | } 233 | return "" 234 | } 235 | 236 | type GrpcCompensateCommand struct { 237 | GlobalTxId string `protobuf:"bytes,1,opt,name=globalTxId,proto3" json:"globalTxId,omitempty"` 238 | LocalTxId string `protobuf:"bytes,2,opt,name=localTxId,proto3" json:"localTxId,omitempty"` 239 | ParentTxId string `protobuf:"bytes,3,opt,name=parentTxId,proto3" json:"parentTxId,omitempty"` 240 | CompensationMethod string `protobuf:"bytes,4,opt,name=compensationMethod,proto3" json:"compensationMethod,omitempty"` 241 | Payloads []byte `protobuf:"bytes,5,opt,name=payloads,proto3" json:"payloads,omitempty"` 242 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 243 | XXX_unrecognized []byte `json:"-"` 244 | XXX_sizecache int32 `json:"-"` 245 | } 246 | 247 | func (m *GrpcCompensateCommand) Reset() { *m = GrpcCompensateCommand{} } 248 | func (m *GrpcCompensateCommand) String() string { return proto.CompactTextString(m) } 249 | func (*GrpcCompensateCommand) ProtoMessage() {} 250 | func (*GrpcCompensateCommand) Descriptor() ([]byte, []int) { 251 | return fileDescriptor_GrpcTxEvent_48a5d9a498bff5ac, []int{3} 252 | } 253 | func (m *GrpcCompensateCommand) XXX_Unmarshal(b []byte) error { 254 | return xxx_messageInfo_GrpcCompensateCommand.Unmarshal(m, b) 255 | } 256 | func (m *GrpcCompensateCommand) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 257 | return xxx_messageInfo_GrpcCompensateCommand.Marshal(b, m, deterministic) 258 | } 259 | func (dst *GrpcCompensateCommand) XXX_Merge(src proto.Message) { 260 | xxx_messageInfo_GrpcCompensateCommand.Merge(dst, src) 261 | } 262 | func (m *GrpcCompensateCommand) XXX_Size() int { 263 | return xxx_messageInfo_GrpcCompensateCommand.Size(m) 264 | } 265 | func (m *GrpcCompensateCommand) XXX_DiscardUnknown() { 266 | xxx_messageInfo_GrpcCompensateCommand.DiscardUnknown(m) 267 | } 268 | 269 | var xxx_messageInfo_GrpcCompensateCommand proto.InternalMessageInfo 270 | 271 | func (m *GrpcCompensateCommand) GetGlobalTxId() string { 272 | if m != nil { 273 | return m.GlobalTxId 274 | } 275 | return "" 276 | } 277 | 278 | func (m *GrpcCompensateCommand) GetLocalTxId() string { 279 | if m != nil { 280 | return m.LocalTxId 281 | } 282 | return "" 283 | } 284 | 285 | func (m *GrpcCompensateCommand) GetParentTxId() string { 286 | if m != nil { 287 | return m.ParentTxId 288 | } 289 | return "" 290 | } 291 | 292 | func (m *GrpcCompensateCommand) GetCompensationMethod() string { 293 | if m != nil { 294 | return m.CompensationMethod 295 | } 296 | return "" 297 | } 298 | 299 | func (m *GrpcCompensateCommand) GetPayloads() []byte { 300 | if m != nil { 301 | return m.Payloads 302 | } 303 | return nil 304 | } 305 | 306 | func init() { 307 | proto.RegisterType((*GrpcServiceConfig)(nil), "GrpcServiceConfig") 308 | proto.RegisterType((*GrpcAck)(nil), "GrpcAck") 309 | proto.RegisterType((*GrpcTxEvent)(nil), "GrpcTxEvent") 310 | proto.RegisterType((*GrpcCompensateCommand)(nil), "GrpcCompensateCommand") 311 | } 312 | 313 | // Reference imports to suppress errors if they are not otherwise used. 314 | var _ context.Context 315 | var _ grpc.ClientConn 316 | 317 | // This is a compile-time assertion to ensure that this generated file 318 | // is compatible with the grpc package it is being compiled against. 319 | const _ = grpc.SupportPackageIsVersion4 320 | 321 | // TxEventServiceClient is the client API for TxEventService service. 322 | // 323 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. 324 | type TxEventServiceClient interface { 325 | OnConnected(ctx context.Context, in *GrpcServiceConfig, opts ...grpc.CallOption) (TxEventService_OnConnectedClient, error) 326 | OnTxEvent(ctx context.Context, in *GrpcTxEvent, opts ...grpc.CallOption) (*GrpcAck, error) 327 | OnDisconnected(ctx context.Context, in *GrpcServiceConfig, opts ...grpc.CallOption) (*GrpcAck, error) 328 | } 329 | 330 | type txEventServiceClient struct { 331 | cc *grpc.ClientConn 332 | } 333 | 334 | func NewTxEventServiceClient(cc *grpc.ClientConn) TxEventServiceClient { 335 | return &txEventServiceClient{cc} 336 | } 337 | 338 | func (c *txEventServiceClient) OnConnected(ctx context.Context, in *GrpcServiceConfig, opts ...grpc.CallOption) (TxEventService_OnConnectedClient, error) { 339 | stream, err := c.cc.NewStream(ctx, &_TxEventService_serviceDesc.Streams[0], "/TxEventService/OnConnected", opts...) 340 | if err != nil { 341 | return nil, err 342 | } 343 | x := &txEventServiceOnConnectedClient{stream} 344 | if err := x.ClientStream.SendMsg(in); err != nil { 345 | return nil, err 346 | } 347 | if err := x.ClientStream.CloseSend(); err != nil { 348 | return nil, err 349 | } 350 | return x, nil 351 | } 352 | 353 | type TxEventService_OnConnectedClient interface { 354 | Recv() (*GrpcCompensateCommand, error) 355 | grpc.ClientStream 356 | } 357 | 358 | type txEventServiceOnConnectedClient struct { 359 | grpc.ClientStream 360 | } 361 | 362 | func (x *txEventServiceOnConnectedClient) Recv() (*GrpcCompensateCommand, error) { 363 | m := new(GrpcCompensateCommand) 364 | if err := x.ClientStream.RecvMsg(m); err != nil { 365 | return nil, err 366 | } 367 | return m, nil 368 | } 369 | 370 | func (c *txEventServiceClient) OnTxEvent(ctx context.Context, in *GrpcTxEvent, opts ...grpc.CallOption) (*GrpcAck, error) { 371 | out := new(GrpcAck) 372 | err := c.cc.Invoke(ctx, "/TxEventService/OnTxEvent", in, out, opts...) 373 | if err != nil { 374 | return nil, err 375 | } 376 | return out, nil 377 | } 378 | 379 | func (c *txEventServiceClient) OnDisconnected(ctx context.Context, in *GrpcServiceConfig, opts ...grpc.CallOption) (*GrpcAck, error) { 380 | out := new(GrpcAck) 381 | err := c.cc.Invoke(ctx, "/TxEventService/OnDisconnected", in, out, opts...) 382 | if err != nil { 383 | return nil, err 384 | } 385 | return out, nil 386 | } 387 | 388 | // TxEventServiceServer is the server API for TxEventService service. 389 | type TxEventServiceServer interface { 390 | OnConnected(*GrpcServiceConfig, TxEventService_OnConnectedServer) error 391 | OnTxEvent(context.Context, *GrpcTxEvent) (*GrpcAck, error) 392 | OnDisconnected(context.Context, *GrpcServiceConfig) (*GrpcAck, error) 393 | } 394 | 395 | func RegisterTxEventServiceServer(s *grpc.Server, srv TxEventServiceServer) { 396 | s.RegisterService(&_TxEventService_serviceDesc, srv) 397 | } 398 | 399 | func _TxEventService_OnConnected_Handler(srv interface{}, stream grpc.ServerStream) error { 400 | m := new(GrpcServiceConfig) 401 | if err := stream.RecvMsg(m); err != nil { 402 | return err 403 | } 404 | return srv.(TxEventServiceServer).OnConnected(m, &txEventServiceOnConnectedServer{stream}) 405 | } 406 | 407 | type TxEventService_OnConnectedServer interface { 408 | Send(*GrpcCompensateCommand) error 409 | grpc.ServerStream 410 | } 411 | 412 | type txEventServiceOnConnectedServer struct { 413 | grpc.ServerStream 414 | } 415 | 416 | func (x *txEventServiceOnConnectedServer) Send(m *GrpcCompensateCommand) error { 417 | return x.ServerStream.SendMsg(m) 418 | } 419 | 420 | func _TxEventService_OnTxEvent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 421 | in := new(GrpcTxEvent) 422 | if err := dec(in); err != nil { 423 | return nil, err 424 | } 425 | if interceptor == nil { 426 | return srv.(TxEventServiceServer).OnTxEvent(ctx, in) 427 | } 428 | info := &grpc.UnaryServerInfo{ 429 | Server: srv, 430 | FullMethod: "/TxEventService/OnTxEvent", 431 | } 432 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 433 | return srv.(TxEventServiceServer).OnTxEvent(ctx, req.(*GrpcTxEvent)) 434 | } 435 | return interceptor(ctx, in, info, handler) 436 | } 437 | 438 | func _TxEventService_OnDisconnected_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 439 | in := new(GrpcServiceConfig) 440 | if err := dec(in); err != nil { 441 | return nil, err 442 | } 443 | if interceptor == nil { 444 | return srv.(TxEventServiceServer).OnDisconnected(ctx, in) 445 | } 446 | info := &grpc.UnaryServerInfo{ 447 | Server: srv, 448 | FullMethod: "/TxEventService/OnDisconnected", 449 | } 450 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 451 | return srv.(TxEventServiceServer).OnDisconnected(ctx, req.(*GrpcServiceConfig)) 452 | } 453 | return interceptor(ctx, in, info, handler) 454 | } 455 | 456 | var _TxEventService_serviceDesc = grpc.ServiceDesc{ 457 | ServiceName: "TxEventService", 458 | HandlerType: (*TxEventServiceServer)(nil), 459 | Methods: []grpc.MethodDesc{ 460 | { 461 | MethodName: "OnTxEvent", 462 | Handler: _TxEventService_OnTxEvent_Handler, 463 | }, 464 | { 465 | MethodName: "OnDisconnected", 466 | Handler: _TxEventService_OnDisconnected_Handler, 467 | }, 468 | }, 469 | Streams: []grpc.StreamDesc{ 470 | { 471 | StreamName: "OnConnected", 472 | Handler: _TxEventService_OnConnected_Handler, 473 | ServerStreams: true, 474 | }, 475 | }, 476 | Metadata: "GrpcTxEvent.proto", 477 | } 478 | 479 | func init() { proto.RegisterFile("GrpcTxEvent.proto", fileDescriptor_GrpcTxEvent_48a5d9a498bff5ac) } 480 | 481 | var fileDescriptor_GrpcTxEvent_48a5d9a498bff5ac = []byte{ 482 | // 458 bytes of a gzipped FileDescriptorProto 483 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x93, 0xcf, 0x6e, 0x13, 0x31, 484 | 0x10, 0xc6, 0xeb, 0x26, 0x69, 0xb2, 0x93, 0xa8, 0x12, 0x96, 0x40, 0xab, 0x08, 0xa1, 0x68, 0x11, 485 | 0x52, 0x4e, 0x56, 0x55, 0xce, 0x1c, 0x68, 0x40, 0x88, 0x03, 0xa4, 0x5a, 0xca, 0x85, 0x0b, 0x72, 486 | 0xbc, 0x6e, 0x58, 0x75, 0xfd, 0x47, 0xf6, 0x50, 0x25, 0x47, 0xde, 0x86, 0xd7, 0xe0, 0xcd, 0x90, 487 | 0xbd, 0x1b, 0x76, 0x49, 0xa3, 0xc0, 0xcd, 0xf3, 0x7d, 0x33, 0x1e, 0xfb, 0xe7, 0x31, 0x3c, 0x7a, 488 | 0xe7, 0xac, 0xb8, 0xd9, 0xbc, 0xbd, 0x97, 0x1a, 0x99, 0x75, 0x06, 0x4d, 0xf6, 0xb9, 0x16, 0x3f, 489 | 0x49, 0x77, 0x5f, 0x0a, 0xb9, 0x30, 0xfa, 0xb6, 0x5c, 0xd3, 0x19, 0x8c, 0x7d, 0x2d, 0x7c, 0xe4, 490 | 0x4a, 0xa6, 0x64, 0x46, 0xe6, 0x49, 0xde, 0x95, 0xe8, 0x33, 0x80, 0x52, 0x7b, 0xe4, 0x5a, 0xc8, 491 | 0xf7, 0x45, 0x7a, 0x1a, 0x13, 0x3a, 0x4a, 0xf6, 0x1c, 0x86, 0x61, 0xdb, 0xd7, 0xe2, 0x8e, 0xa6, 492 | 0x30, 0xe4, 0x2b, 0xe3, 0x50, 0x16, 0x71, 0xa3, 0x51, 0xbe, 0x0b, 0xb3, 0x1f, 0x3d, 0x18, 0x77, 493 | 0x4e, 0x44, 0x9f, 0x42, 0x82, 0xa5, 0x92, 0x1e, 0xb9, 0xb2, 0x31, 0xb7, 0x97, 0xb7, 0x42, 0x68, 494 | 0xb9, 0xae, 0xcc, 0x8a, 0x57, 0x37, 0x9b, 0xb6, 0x65, 0xab, 0x84, 0xea, 0xca, 0x88, 0xc6, 0xee, 495 | 0x45, 0xbb, 0x15, 0x42, 0xb5, 0xe5, 0x4e, 0x6a, 0x8c, 0x76, 0xbf, 0xae, 0x6e, 0x15, 0x4a, 0xa1, 496 | 0x8f, 0x5b, 0x2b, 0xd3, 0x41, 0x74, 0xe2, 0x9a, 0x32, 0xa0, 0xc2, 0x28, 0x2b, 0xb5, 0xe7, 0x58, 497 | 0x1a, 0xfd, 0x41, 0xe2, 0x37, 0x53, 0xa4, 0x67, 0x31, 0xe3, 0x80, 0x43, 0xa7, 0x30, 0xb2, 0x7c, 498 | 0x5b, 0x19, 0x5e, 0xf8, 0x74, 0x38, 0x23, 0xf3, 0x49, 0xfe, 0x27, 0xde, 0x47, 0x3a, 0xfa, 0x17, 499 | 0xd2, 0x64, 0x1f, 0x69, 0xe0, 0x18, 0x60, 0x98, 0xef, 0x98, 0xc2, 0x8c, 0xcc, 0x07, 0xf9, 0x2e, 500 | 0x0c, 0x8e, 0x93, 0xe8, 0x4a, 0xe9, 0xd3, 0x71, 0xed, 0x34, 0x61, 0xe8, 0x1a, 0x96, 0xdb, 0xe6, 501 | 0xe8, 0x93, 0xba, 0x6b, 0x47, 0xca, 0x7e, 0x11, 0x78, 0x1c, 0xde, 0x60, 0xb1, 0xbb, 0x8e, 0x5c, 502 | 0x18, 0xa5, 0xb8, 0x2e, 0xf6, 0x78, 0x93, 0xe3, 0xbc, 0x4f, 0x8f, 0xf3, 0xee, 0x3d, 0xe0, 0x7d, 503 | 0x98, 0x6d, 0xff, 0xbf, 0xd8, 0x0e, 0xfe, 0x66, 0x7b, 0xf9, 0x93, 0xc0, 0x79, 0x33, 0x43, 0xcd, 504 | 0x1c, 0xd3, 0x57, 0x30, 0x5e, 0xea, 0x85, 0xd1, 0x5a, 0x0a, 0x94, 0x05, 0xa5, 0xec, 0xc1, 0x90, 505 | 0x4f, 0x9f, 0xb0, 0x83, 0xf7, 0xce, 0x4e, 0x2e, 0x08, 0x7d, 0x01, 0xc9, 0x52, 0xef, 0xc6, 0x72, 506 | 0xc2, 0x3a, 0x43, 0x3a, 0x1d, 0xb1, 0x66, 0xb0, 0xb3, 0x13, 0x7a, 0x01, 0xe7, 0x4b, 0xfd, 0xa6, 507 | 0xf4, 0xe2, 0x68, 0xa3, 0x4e, 0xc5, 0xd5, 0x12, 0x2e, 0x85, 0x51, 0x0c, 0x3d, 0x72, 0x71, 0xc7, 508 | 0x14, 0x47, 0x57, 0x6e, 0x98, 0xad, 0x38, 0xde, 0x1a, 0xa7, 0x98, 0xe7, 0x6b, 0xce, 0x6c, 0x70, 509 | 0x84, 0xd1, 0xe8, 0xb8, 0x40, 0xb6, 0x76, 0x56, 0x5c, 0x4d, 0x9a, 0xe6, 0xd7, 0xe1, 0xcb, 0x5e, 510 | 0x93, 0x2f, 0x49, 0x48, 0xfc, 0x1a, 0xac, 0xd5, 0x59, 0xfc, 0xc6, 0x2f, 0x7f, 0x07, 0x00, 0x00, 511 | 0xff, 0xff, 0x75, 0xaa, 0xa0, 0xab, 0xdb, 0x03, 0x00, 0x00, 512 | } 513 | -------------------------------------------------------------------------------- /saga_grpc/GrpcTxEvent.proto: -------------------------------------------------------------------------------- 1 | // 2 | // Licensed to the Apache Software Foundation (ASF) under one or more 3 | // contributor license agreements. See the NOTICE file distributed with 4 | // this work for additional information regarding copyright ownership. 5 | // The ASF licenses this file to You under the Apache License, Version 2.0 6 | // (the "License"); you may not use this file except in compliance with 7 | // the License. You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | 18 | syntax = "proto3"; 19 | 20 | option java_multiple_files = true; 21 | option java_package = "org.apache.servicecomb.pack.contract.grpc"; 22 | option java_outer_classname = "TxEventProto"; 23 | 24 | option go_package = "saga_grpc"; 25 | 26 | message GrpcServiceConfig { 27 | string serviceName = 1; 28 | string instanceId = 2; 29 | } 30 | 31 | message GrpcAck { 32 | bool aborted = 1; 33 | } 34 | 35 | service TxEventService { 36 | rpc OnConnected (GrpcServiceConfig) returns (stream GrpcCompensateCommand) { 37 | } 38 | rpc OnTxEvent (GrpcTxEvent) returns (GrpcAck) {} 39 | rpc OnDisconnected (GrpcServiceConfig) returns (GrpcAck) { 40 | } 41 | } 42 | 43 | message GrpcTxEvent { 44 | int64 timestamp = 1; 45 | string globalTxId = 2; 46 | string localTxId = 3; 47 | string parentTxId = 4; 48 | string type = 5; 49 | string compensationMethod = 6; 50 | bytes payloads = 7; 51 | string serviceName = 8; 52 | string instanceId = 9; 53 | int32 timeout = 10; 54 | int32 retries = 11; 55 | string retryMethod = 12; 56 | } 57 | 58 | message GrpcCompensateCommand { 59 | string globalTxId = 1; 60 | string localTxId = 2; 61 | string parentTxId = 3; 62 | string compensationMethod = 4; 63 | bytes payloads = 5; 64 | } 65 | 66 | -------------------------------------------------------------------------------- /sagatx.go: -------------------------------------------------------------------------------- 1 | package saga 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "github.com/jeremyxu2010/matrix-saga-go/config" 8 | "github.com/jeremyxu2010/matrix-saga-go/constants" 9 | sagactx "github.com/jeremyxu2010/matrix-saga-go/context" 10 | "github.com/jeremyxu2010/matrix-saga-go/degorator" 11 | "github.com/jeremyxu2010/matrix-saga-go/log" 12 | "github.com/jeremyxu2010/matrix-saga-go/metadata" 13 | "github.com/jeremyxu2010/matrix-saga-go/processor" 14 | "github.com/jeremyxu2010/matrix-saga-go/serializer" 15 | "github.com/jeremyxu2010/matrix-saga-go/transport" 16 | "github.com/jeremyxu2010/matrix-saga-go/utils" 17 | "reflect" 18 | "sync" 19 | ) 20 | 21 | var ( 22 | initOnce sync.Once 23 | 24 | compensationProcessor *processor.CompensationProcessor 25 | serviceConfig *config.ServiceConfig 26 | transportContractor *transport.TransportContractor 27 | s serializer.Serializer 28 | 29 | logger log.Logger 30 | ) 31 | 32 | func init() { 33 | s = serializer.NewGobSerializer() 34 | compensationProcessor = processor.NewCompensationProcessor(s, logger) 35 | } 36 | 37 | func DecorateSagaStartMethod(sagaStartPtr interface{}, target interface{}, timeout int, autoClose bool) error { 38 | sagaStartInjectBefore := func(ctx context.Context) error { 39 | sagaAgentCtx := sagactx.NewSagaAgentContext() 40 | sagaAgentCtx.Initialize() 41 | _, err := transportContractor.SendSagaStartedEvent(sagaAgentCtx, timeout) 42 | if err != nil { 43 | transportContractor.SendTxAbortedEvent(sagaAgentCtx, "", utils.GetFnName(target), err) 44 | return err 45 | } 46 | logger.LogDebug(fmt.Sprintf("Initialized context %v before execution of method %v", sagaAgentCtx, target)) 47 | return nil 48 | } 49 | 50 | sagaStartInjectAfter := func(ctx context.Context) error { 51 | defer func() { 52 | if autoClose { 53 | sagactx.ClearSagaAgentContext() 54 | } 55 | }() 56 | sagaAgentCtx, err := sagactx.GetSagaAgentContext() 57 | if err != nil { 58 | transportContractor.SendTxAbortedEvent(sagaAgentCtx, "", utils.GetFnName(target), err) 59 | logger.LogError(fmt.Sprintf("Transaction %v failed.", sagaAgentCtx)) 60 | return err 61 | } 62 | if autoClose { 63 | return sendSagaEndEvent(ctx, sagaAgentCtx, target) 64 | } else { 65 | logger.LogDebug(fmt.Sprintf("Transaction with context %v is not finished in the SagaStarted annotated method.", sagaAgentCtx)) 66 | return nil 67 | } 68 | } 69 | 70 | err := degorator.Decorate(sagaStartPtr, target, sagaStartInjectBefore, sagaStartInjectAfter) 71 | if err != nil { 72 | return err 73 | } 74 | return nil 75 | } 76 | 77 | func DecorateSagaEndMethod(sagaStartPtr interface{}, target interface{}) error { 78 | sagaEndInjectBefore := func(ctx context.Context) error { 79 | return nil 80 | } 81 | sagaEndInjectAfter := func(ctx context.Context) error { 82 | defer func() { 83 | sagactx.ClearSagaAgentContext() 84 | }() 85 | sagaAgentCtx, err := sagactx.GetSagaAgentContext() 86 | if err != nil { 87 | transportContractor.SendTxAbortedEvent(sagaAgentCtx, "", utils.GetFnName(target), err) 88 | logger.LogError(fmt.Sprintf("Transaction %v failed.", sagaAgentCtx)) 89 | return err 90 | } 91 | return sendSagaEndEvent(ctx, sagaAgentCtx, target) 92 | } 93 | err := degorator.Decorate(sagaStartPtr, target, sagaEndInjectBefore, sagaEndInjectAfter) 94 | if err != nil { 95 | return err 96 | } 97 | return nil 98 | } 99 | 100 | func sendSagaEndEvent(ctx context.Context, sagaAgentCtx *sagactx.SagaAgentContext, target interface{}) error { 101 | if m, ok := metadata.FromContext(ctx); ok { 102 | if m[constants.KEY_FUNCTION_CALL_ERROR] != nil { 103 | if v, ok := m[constants.KEY_FUNCTION_CALL_ERROR].(error); ok { 104 | err := v.(error) 105 | transportContractor.SendTxAbortedEvent(sagaAgentCtx, "", utils.GetFnName(target), err) 106 | logger.LogError(fmt.Sprintf("Transaction %v failed.", sagaAgentCtx)) 107 | return err 108 | } 109 | } 110 | } 111 | aborted, err := transportContractor.SendSagaEndedEvent(sagaAgentCtx) 112 | if err != nil { 113 | transportContractor.SendTxAbortedEvent(sagaAgentCtx, "", utils.GetFnName(target), err) 114 | logger.LogError(fmt.Sprintf("Transaction %v failed.", sagaAgentCtx)) 115 | return err 116 | } 117 | 118 | if aborted { 119 | return errors.New(fmt.Sprintf("transaction %s is aborted", sagaAgentCtx.GlobalTxId)) 120 | } 121 | logger.LogDebug(fmt.Sprintf("Transaction with context %v has finished.", sagaAgentCtx)) 122 | return nil 123 | } 124 | 125 | func DecorateCompensableMethod(compensablePtr interface{}, target interface{}, compensed interface{}, timeout int) error { 126 | 127 | compensedFuncName := utils.GetFnName(compensed) 128 | 129 | compensationProcessor.RegisterCompensationFunc(compensedFuncName, compensed) 130 | 131 | compensableInjectBefore := func(ctx context.Context) error { 132 | sagaAgentCtx, err := sagactx.GetSagaAgentContext() 133 | if err != nil { 134 | return err 135 | } 136 | parentLocalTxId := sagaAgentCtx.LocalTxId 137 | if m, ok := metadata.FromContext(ctx); ok { 138 | m[constants.KEY_PARENT_LOCAL_TX_ID] = parentLocalTxId 139 | } 140 | sagaAgentCtx.NewLocalTxId() 141 | logger.LogDebug(fmt.Sprintf("Updated context %v for compensable method %v", sagaAgentCtx, target)) 142 | logger.LogDebug(fmt.Sprintf("Intercepting compensable method %v with context %v", target, sagaAgentCtx)) 143 | var args []reflect.Value 144 | if m, ok := metadata.FromContext(ctx); ok { 145 | if v, ok := m[constants.KEY_FUNCTION_CALL_ARGS].([]reflect.Value); ok { 146 | args = v 147 | } 148 | } 149 | aborted, err := transportContractor.SendTxStartedEvent(sagaAgentCtx, parentLocalTxId, compensedFuncName, timeout, args) 150 | if err != nil { 151 | sagaAgentCtx.LocalTxId = parentLocalTxId 152 | logger.LogDebug(fmt.Sprintf("Restored context back to %v", sagaAgentCtx)) 153 | return err 154 | } 155 | if aborted { 156 | abortedLocalTxId := sagaAgentCtx.LocalTxId 157 | sagaAgentCtx.LocalTxId = parentLocalTxId 158 | logger.LogDebug(fmt.Sprintf("Restored context back to %v", sagaAgentCtx)) 159 | return errors.New(fmt.Sprintf("Abort sub transaction %s because global transaction %s has already aborted.", abortedLocalTxId, sagaAgentCtx.GlobalTxId)) 160 | } 161 | return nil 162 | } 163 | 164 | compensableInjectAfter := func(ctx context.Context) error { 165 | var parentLocalTxId string 166 | if m, ok := metadata.FromContext(ctx); ok { 167 | if v, ok := m[constants.KEY_PARENT_LOCAL_TX_ID].(string); ok { 168 | parentLocalTxId = v 169 | } 170 | } 171 | sagaAgentCtx, err := sagactx.GetSagaAgentContext() 172 | if err != nil { 173 | return err 174 | } 175 | if m, ok := metadata.FromContext(ctx); ok { 176 | if m[constants.KEY_FUNCTION_CALL_ERROR] != nil { 177 | if v, ok := m[constants.KEY_FUNCTION_CALL_ERROR].(error); ok { 178 | err = v.(error) 179 | transportContractor.SendTxAbortedEvent(sagaAgentCtx, "", utils.GetFnName(target), err) 180 | logger.LogError(fmt.Sprintf("Transaction %v failed.", sagaAgentCtx)) 181 | return err 182 | } 183 | } 184 | } 185 | aborted, err := transportContractor.SendTxEndedEvent(sagaAgentCtx, parentLocalTxId, compensedFuncName) 186 | if err != nil { 187 | transportContractor.SendTxAbortedEvent(sagaAgentCtx, "", utils.GetFnName(target), err) 188 | logger.LogError(fmt.Sprintf("Transaction %v failed.", sagaAgentCtx)) 189 | sagaAgentCtx.LocalTxId = parentLocalTxId 190 | logger.LogDebug(fmt.Sprintf("Restored context back to %v", sagaAgentCtx)) 191 | return err 192 | } 193 | 194 | if aborted { 195 | sagaAgentCtx.LocalTxId = parentLocalTxId 196 | logger.LogDebug(fmt.Sprintf("Restored context back to %v", sagaAgentCtx)) 197 | return errors.New(fmt.Sprintf("transaction %s is aborted", parentLocalTxId)) 198 | } 199 | sagaAgentCtx.LocalTxId = parentLocalTxId 200 | logger.LogDebug(fmt.Sprintf("Restored context back to %v", sagaAgentCtx)) 201 | return nil 202 | } 203 | 204 | err := degorator.Decorate(compensablePtr, target, compensableInjectBefore, compensableInjectAfter) 205 | if err != nil { 206 | return err 207 | } 208 | return nil 209 | } 210 | 211 | func InitSagaAgent(serviceName string, coordinatorAddress string, l log.Logger) error { 212 | initOnce.Do(func() { 213 | logger = l 214 | if logger == nil { 215 | logger = log.NewNoopLogger() 216 | } 217 | serviceConfig = config.NewServiceConfig(serviceName) 218 | transportContractor = transport.NewTransportContractor(coordinatorAddress, serviceConfig, compensationProcessor, s, logger) 219 | }) 220 | err := transportContractor.Connect() 221 | if err != nil { 222 | return err 223 | } 224 | return nil 225 | } 226 | -------------------------------------------------------------------------------- /serializer/api.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import "reflect" 4 | 5 | type Serializer interface { 6 | Unserialize([]byte)([]reflect.Value, error) 7 | Serialize([]reflect.Value)([]byte, error) 8 | } 9 | -------------------------------------------------------------------------------- /serializer/gob_serializer.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "reflect" 7 | "errors" 8 | "fmt" 9 | ) 10 | 11 | type GobSerializer struct { 12 | } 13 | 14 | func NewGobSerializer()Serializer{ 15 | return &GobSerializer{} 16 | } 17 | 18 | func (s *GobSerializer)Unserialize(payloads []byte)([]reflect.Value, error){ 19 | b := bytes.NewReader(payloads) 20 | dec := gob.NewDecoder(b) 21 | args := make([]interface{}, 0) 22 | err := dec.Decode(&args) 23 | if err != nil { 24 | return nil, errors.New(fmt.Sprintf("decode values failed, %v", err)) 25 | } 26 | values := make([]reflect.Value, 0) 27 | for _, arg := range args { 28 | values = append(values, reflect.ValueOf(arg)) 29 | } 30 | return values, nil 31 | } 32 | 33 | func (s *GobSerializer)Serialize(values []reflect.Value)([]byte, error){ 34 | var b bytes.Buffer 35 | enc := gob.NewEncoder(&b) 36 | args := make([]interface{}, 0) 37 | for _, value := range values { 38 | args = append(args, value.Interface()) 39 | } 40 | err := enc.Encode(args) 41 | if err != nil { 42 | return nil, errors.New(fmt.Sprintf("encode args failed, %v", err)) 43 | } 44 | return b.Bytes(), nil 45 | } 46 | -------------------------------------------------------------------------------- /test/old/demo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | ) 10 | 11 | var ( 12 | BALANCES map[string]int 13 | ) 14 | 15 | func init() { 16 | initDatas() 17 | } 18 | 19 | func initDatas(){ 20 | BALANCES = make(map[string]int, 0) 21 | BALANCES["foo"] = 500 22 | BALANCES["bar"] = 500 23 | } 24 | 25 | func TransferMoney() error { 26 | err := TransferOut("foo", 100) 27 | if err != nil { 28 | return err 29 | } 30 | err = TransferIn("bar", 100) 31 | if err != nil { 32 | return err 33 | } 34 | return nil 35 | } 36 | 37 | func TransferOut(from string, amount int) error { 38 | oldAmount, _ := BALANCES[from] 39 | BALANCES[from] = oldAmount - amount 40 | return nil 41 | } 42 | 43 | func TransferIn(to string, amount int) error { 44 | oldAmount, _ := BALANCES[to] 45 | BALANCES[to] = oldAmount + amount 46 | return nil 47 | } 48 | 49 | func main() { 50 | TransferMoney() 51 | stopped := false 52 | go func() { 53 | s := make(chan os.Signal) 54 | signal.Notify(s, syscall.SIGINT, syscall.SIGTERM) 55 | <-s 56 | stopped = true 57 | }() 58 | for !stopped { 59 | fmt.Println(BALANCES["foo"], BALANCES["bar"]) 60 | time.Sleep(time.Second * 3) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /test/sagaend/sagatx_demo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/jeremyxu2010/matrix-saga-go" 7 | "github.com/jeremyxu2010/matrix-saga-go/utils" 8 | "os" 9 | "os/signal" 10 | "syscall" 11 | "time" 12 | ) 13 | 14 | var ( 15 | BALANCES map[string]int 16 | TransferMoneySagaStartDecorated func() error 17 | TransferOutCompensableDecorated func(from string, amount int) error 18 | TransferInCompensableDecorated func(to string, amount int) error 19 | TransferMoneySagaEndDecorated func() error 20 | ) 21 | 22 | func init() { 23 | err := saga.DecorateSagaStartMethod(&TransferMoneySagaStartDecorated, TransferMoney, 20, false) 24 | if err != nil { 25 | panic(err) 26 | } 27 | err = saga.DecorateCompensableMethod(&TransferOutCompensableDecorated, TransferOut, CancelTransferOut, 5) 28 | if err != nil { 29 | panic(err) 30 | } 31 | err = saga.DecorateCompensableMethod(&TransferInCompensableDecorated, TransferIn, CancelTransferIn, 5) 32 | if err != nil { 33 | panic(err) 34 | } 35 | err = saga.DecorateSagaEndMethod(&TransferMoneySagaEndDecorated, TransferMoneyEnd) 36 | if err != nil { 37 | panic(err) 38 | } 39 | 40 | initDatas() 41 | } 42 | 43 | func initDatas() { 44 | BALANCES = make(map[string]int, 0) 45 | BALANCES["foo"] = 500 46 | BALANCES["bar"] = 500 47 | } 48 | 49 | func TransferMoney() error { 50 | err := TransferOutCompensableDecorated("foo", 100) 51 | if err != nil { 52 | return err 53 | } 54 | err = TransferInCompensableDecorated("bar", 100) 55 | if err != nil { 56 | return err 57 | } 58 | return nil 59 | } 60 | 61 | func TransferMoneyEnd() error { 62 | return nil 63 | } 64 | 65 | func TransferOut(from string, amount int) error { 66 | //ctx, _ := sagactx.GetSagaAgentContext() 67 | //fmt.Println(ctx.GlobalTxId, ctx.LocalTxId) 68 | 69 | oldAmount, _ := BALANCES[from] 70 | BALANCES[from] = oldAmount - amount 71 | return nil 72 | } 73 | 74 | func CancelTransferOut(from string, amount int) error { 75 | //ctx, _ := sagactx.GetSagaAgentContext() 76 | //fmt.Println(ctx.GlobalTxId, ctx.LocalTxId) 77 | 78 | oldAmount, _ := BALANCES[from] 79 | BALANCES[from] = oldAmount + amount 80 | return nil 81 | } 82 | 83 | func TransferIn(to string, amount int) error { 84 | //ctx, _ := sagactx.GetSagaAgentContext() 85 | //fmt.Println(ctx.GlobalTxId, ctx.LocalTxId) 86 | 87 | //oldAmount, _ := BALANCES[to] 88 | //BALANCES[to] = oldAmount + amount 89 | //return nil 90 | 91 | return errors.New("xx") 92 | } 93 | 94 | func CancelTransferIn(to string, amount int) error { 95 | oldAmount, _ := BALANCES[to] 96 | BALANCES[to] = oldAmount - amount 97 | return nil 98 | } 99 | 100 | func main() { 101 | utils.DisableHttpProxy() 102 | // 这里的第一个参数是本服务的名称,第二个参数是alpha-server的地址,第三个参数是Logger 103 | saga.InitSagaAgent("saga-go-demo", "127.0.0.1:8080", nil) 104 | fmt.Printf("foo balance: %d, bar balance: %d\n", BALANCES["foo"], BALANCES["bar"]) 105 | TransferMoneySagaStartDecorated() 106 | TransferMoneySagaEndDecorated() 107 | stopped := false 108 | go func() { 109 | s := make(chan os.Signal) 110 | signal.Notify(s, syscall.SIGINT, syscall.SIGTERM) 111 | <-s 112 | stopped = true 113 | }() 114 | for !stopped { 115 | fmt.Printf("foo balance: %d, bar balance: %d\n", BALANCES["foo"], BALANCES["bar"]) 116 | time.Sleep(time.Second * 3) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /test/sagatx_demo.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/jeremyxu2010/matrix-saga-go" 7 | "github.com/jeremyxu2010/matrix-saga-go/utils" 8 | "os" 9 | "os/signal" 10 | "syscall" 11 | "time" 12 | ) 13 | 14 | var ( 15 | BALANCES map[string]int 16 | TransferMoneySagaStartDecorated func() error 17 | TransferOutCompensableDecorated func(from string, amount int) error 18 | TransferInCompensableDecorated func(to string, amount int) error 19 | ) 20 | 21 | func init() { 22 | err := saga.DecorateSagaStartMethod(&TransferMoneySagaStartDecorated, TransferMoney, 20, true) 23 | if err != nil { 24 | panic(err) 25 | } 26 | err = saga.DecorateCompensableMethod(&TransferOutCompensableDecorated, TransferOut, CancelTransferOut, 5) 27 | if err != nil { 28 | panic(err) 29 | } 30 | err = saga.DecorateCompensableMethod(&TransferInCompensableDecorated, TransferIn, CancelTransferIn, 5) 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | initDatas() 36 | } 37 | 38 | func initDatas() { 39 | BALANCES = make(map[string]int, 0) 40 | BALANCES["foo"] = 500 41 | BALANCES["bar"] = 500 42 | } 43 | 44 | func TransferMoney() error { 45 | err := TransferOutCompensableDecorated("foo", 100) 46 | if err != nil { 47 | return err 48 | } 49 | err = TransferInCompensableDecorated("bar", 100) 50 | if err != nil { 51 | return err 52 | } 53 | return nil 54 | } 55 | 56 | func TransferOut(from string, amount int) error { 57 | //ctx, _ := sagactx.GetSagaAgentContext() 58 | //fmt.Println(ctx.GlobalTxId, ctx.LocalTxId) 59 | 60 | oldAmount, _ := BALANCES[from] 61 | BALANCES[from] = oldAmount - amount 62 | return nil 63 | } 64 | 65 | func CancelTransferOut(from string, amount int) error { 66 | //ctx, _ := sagactx.GetSagaAgentContext() 67 | //fmt.Println(ctx.GlobalTxId, ctx.LocalTxId) 68 | 69 | oldAmount, _ := BALANCES[from] 70 | BALANCES[from] = oldAmount + amount 71 | return nil 72 | } 73 | 74 | func TransferIn(to string, amount int) error { 75 | //ctx, _ := sagactx.GetSagaAgentContext() 76 | //fmt.Println(ctx.GlobalTxId, ctx.LocalTxId) 77 | 78 | //oldAmount, _ := BALANCES[to] 79 | //BALANCES[to] = oldAmount + amount 80 | //return nil 81 | 82 | return errors.New("xx") 83 | } 84 | 85 | func CancelTransferIn(to string, amount int) error { 86 | oldAmount, _ := BALANCES[to] 87 | BALANCES[to] = oldAmount - amount 88 | return nil 89 | } 90 | 91 | func main() { 92 | utils.DisableHttpProxy() 93 | // 这里的第一个参数是本服务的名称,第二个参数是alpha-server的地址,第三个参数是Logger 94 | saga.InitSagaAgent("saga-go-demo", "127.0.0.1:8080", nil) 95 | fmt.Printf("foo balance: %d, bar balance: %d\n", BALANCES["foo"], BALANCES["bar"]) 96 | TransferMoneySagaStartDecorated() 97 | stopped := false 98 | go func() { 99 | s := make(chan os.Signal) 100 | signal.Notify(s, syscall.SIGINT, syscall.SIGTERM) 101 | <-s 102 | stopped = true 103 | }() 104 | for !stopped { 105 | fmt.Printf("foo balance: %d, bar balance: %d\n", BALANCES["foo"], BALANCES["bar"]) 106 | time.Sleep(time.Second * 3) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /transport/transport_contractor.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "google.golang.org/grpc" 5 | "time" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | "reflect" 10 | "github.com/jeremyxu2010/matrix-saga-go/saga_grpc" 11 | "context" 12 | "github.com/jeremyxu2010/matrix-saga-go/constants" 13 | "github.com/jeremyxu2010/matrix-saga-go/processor" 14 | sagactx "github.com/jeremyxu2010/matrix-saga-go/context" 15 | "github.com/jeremyxu2010/matrix-saga-go/log" 16 | "fmt" 17 | "github.com/jeremyxu2010/matrix-saga-go/config" 18 | "github.com/jeremyxu2010/matrix-saga-go/serializer" 19 | ) 20 | 21 | type TransportContractor struct { 22 | coordinatorAddress string 23 | txEventServiceClient saga_grpc.TxEventServiceClient 24 | onConnectedClient saga_grpc.TxEventService_OnConnectedClient 25 | serviceConfig *config.ServiceConfig 26 | compensationProcessor *processor.CompensationProcessor 27 | logger log.Logger 28 | pendingTasks chan func() 29 | s serializer.Serializer 30 | } 31 | 32 | func NewTransportContractor(coordinatorAddress string, serviceConfig *config.ServiceConfig, compensationProcessor *processor.CompensationProcessor, s serializer.Serializer, logger log.Logger) *TransportContractor { 33 | transportContractor := &TransportContractor{ 34 | coordinatorAddress: coordinatorAddress, 35 | serviceConfig: serviceConfig, 36 | compensationProcessor: compensationProcessor, 37 | logger: logger, 38 | pendingTasks: make(chan func(), 0), 39 | s: s, 40 | } 41 | go transportContractor.scheduleProcessReconnectTask() 42 | return transportContractor 43 | } 44 | 45 | func (c *TransportContractor) Connect() error { 46 | err := c.initClient() 47 | if err != nil { 48 | return err 49 | } 50 | c.onConnected() 51 | return nil 52 | } 53 | 54 | func (c *TransportContractor) initClient() error { 55 | ctx, cancel := context.WithTimeout(context.Background(), constants.GRPC_COMMUNICATE_TIMEOUT) 56 | defer cancel() 57 | c.logger.LogInfo(fmt.Sprintf("connect coordinator with address[%s]", c.coordinatorAddress)) 58 | conn, err := grpc.DialContext(ctx, c.coordinatorAddress, grpc.WithInsecure()) 59 | if err != nil { 60 | return err 61 | } 62 | c.txEventServiceClient = saga_grpc.NewTxEventServiceClient(conn) 63 | return nil 64 | } 65 | 66 | func (c *TransportContractor) onConnected() error { 67 | //ctx, cancel := context.WithTimeout(context.Background(), constants.GRPC_COMMUNICATE_TIMEOUT) 68 | //defer cancel() 69 | grpcServiceConfig := &saga_grpc.GrpcServiceConfig{ 70 | ServiceName: c.serviceConfig.ServiceName, 71 | InstanceId: c.serviceConfig.InstanceId, 72 | } 73 | var err error 74 | c.onConnectedClient, err = c.txEventServiceClient.OnConnected(context.Background(), grpcServiceConfig) 75 | if err != nil { 76 | return err 77 | } 78 | stopped := false 79 | go func() { 80 | for !stopped { 81 | grpcCompensateCommand, err := c.onConnectedClient.Recv() 82 | if err != nil { 83 | if !stopped { 84 | c.logger.LogError(fmt.Sprintf("failed to process grpc compensate command, err: %v", err)) 85 | // create reconnect task 86 | c.pendingTasks <- c.reconnectTask 87 | } 88 | continue 89 | } 90 | c.logger.LogInfo(fmt.Sprintf("Received compensate command, global tx id: %s, local tx id: %s, compensation method: %s", 91 | grpcCompensateCommand.GlobalTxId, grpcCompensateCommand.LocalTxId, grpcCompensateCommand.CompensationMethod)) 92 | c.compensationProcessor.ExecuteCompensate(grpcCompensateCommand.GlobalTxId, grpcCompensateCommand.LocalTxId, grpcCompensateCommand.CompensationMethod, grpcCompensateCommand.Payloads) 93 | c.sendTxCompensatedEvent(grpcCompensateCommand.GlobalTxId, grpcCompensateCommand.LocalTxId, grpcCompensateCommand.ParentTxId, grpcCompensateCommand.CompensationMethod) 94 | } 95 | }() 96 | go func() { 97 | s := make(chan os.Signal) 98 | signal.Notify(s, syscall.SIGINT, syscall.SIGTERM) 99 | <-s 100 | stopped = true 101 | ctx, cancel := context.WithTimeout(context.Background(), constants.GRPC_COMMUNICATE_TIMEOUT) 102 | defer cancel() 103 | c.txEventServiceClient.OnDisconnected(ctx, grpcServiceConfig) 104 | 105 | }() 106 | return nil 107 | } 108 | 109 | func (c *TransportContractor) sendTxCompensatedEvent(globalTxId string, localTxId string, parentTxId string, compensationMethod string) { 110 | e := &saga_grpc.GrpcTxEvent{ 111 | ServiceName: c.serviceConfig.ServiceName, 112 | InstanceId: c.serviceConfig.InstanceId, 113 | Timestamp: time.Now().UnixNano() / int64(time.Millisecond), 114 | GlobalTxId: globalTxId, 115 | LocalTxId: localTxId, 116 | ParentTxId: parentTxId, 117 | Type: constants.EVENT_NAME_TXCOMPENSATEDEVENT, 118 | Timeout: int32(0), 119 | CompensationMethod: compensationMethod, 120 | RetryMethod: "", 121 | Retries: 0, 122 | Payloads: nil, 123 | } 124 | ctx, cancel := context.WithTimeout(context.Background(), constants.GRPC_COMMUNICATE_TIMEOUT) 125 | defer cancel() 126 | c.txEventServiceClient.OnTxEvent(ctx, e) 127 | } 128 | 129 | func (c *TransportContractor) SendSagaStartedEvent(sagaAgentCtx *sagactx.SagaAgentContext, timeout int) (bool, error) { 130 | e := &saga_grpc.GrpcTxEvent{ 131 | ServiceName: c.serviceConfig.ServiceName, 132 | InstanceId: c.serviceConfig.InstanceId, 133 | Timestamp: time.Now().UnixNano() / int64(time.Millisecond), 134 | GlobalTxId: sagaAgentCtx.GlobalTxId, 135 | LocalTxId: sagaAgentCtx.LocalTxId, 136 | ParentTxId: "", 137 | Type: constants.EVENT_NAME_SAGASTARTEDEVENT, 138 | Timeout: int32(timeout), 139 | CompensationMethod: "", 140 | RetryMethod: "", 141 | Retries: 0, 142 | Payloads: nil, 143 | } 144 | ctx, cancel := context.WithTimeout(context.Background(), constants.GRPC_COMMUNICATE_TIMEOUT) 145 | defer cancel() 146 | grpcAck, err := c.txEventServiceClient.OnTxEvent(ctx, e) 147 | if err != nil { 148 | return false, err 149 | } 150 | return grpcAck.Aborted, nil 151 | } 152 | 153 | func (c *TransportContractor) SendSagaEndedEvent(sagaAgentCtx *sagactx.SagaAgentContext) (bool, error) { 154 | e := &saga_grpc.GrpcTxEvent{ 155 | ServiceName: c.serviceConfig.ServiceName, 156 | InstanceId: c.serviceConfig.InstanceId, 157 | Timestamp: time.Now().UnixNano() / int64(time.Millisecond), 158 | GlobalTxId: sagaAgentCtx.GlobalTxId, 159 | LocalTxId: sagaAgentCtx.LocalTxId, 160 | ParentTxId: "", 161 | Type: constants.EVENT_NAME_SAGAENDEDEVENT, 162 | Timeout: int32(0), 163 | CompensationMethod: "", 164 | RetryMethod: "", 165 | Retries: 0, 166 | Payloads: nil, 167 | } 168 | ctx, cancel := context.WithTimeout(context.Background(), constants.GRPC_COMMUNICATE_TIMEOUT) 169 | defer cancel() 170 | grpcAck, err := c.txEventServiceClient.OnTxEvent(ctx, e) 171 | if err != nil { 172 | return false, err 173 | } 174 | return grpcAck.Aborted, nil 175 | } 176 | 177 | func (c *TransportContractor) SendTxStartedEvent(sagaAgentCtx *sagactx.SagaAgentContext, parentTxId string, compensationMethod string, timeout int, args []reflect.Value) (bool, error) { 178 | b, err := c.s.Serialize(args) 179 | if err != nil { 180 | c.logger.LogError(fmt.Sprintf("%v", err)) 181 | return false, err 182 | } 183 | e := &saga_grpc.GrpcTxEvent{ 184 | ServiceName: c.serviceConfig.ServiceName, 185 | InstanceId: c.serviceConfig.InstanceId, 186 | Timestamp: time.Now().UnixNano() / int64(time.Millisecond), 187 | GlobalTxId: sagaAgentCtx.GlobalTxId, 188 | LocalTxId: sagaAgentCtx.LocalTxId, 189 | ParentTxId: parentTxId, 190 | Type: constants.EVENT_NAME_TXSTARTEDEVENT, 191 | Timeout: int32(timeout), 192 | CompensationMethod: compensationMethod, 193 | RetryMethod: "", 194 | Retries: 0, 195 | Payloads: b, 196 | } 197 | ctx, cancel := context.WithTimeout(context.Background(), constants.GRPC_COMMUNICATE_TIMEOUT) 198 | defer cancel() 199 | grpcAck, err := c.txEventServiceClient.OnTxEvent(ctx, e) 200 | if err != nil { 201 | return false, err 202 | } 203 | return grpcAck.Aborted, nil 204 | } 205 | 206 | func (c *TransportContractor) SendTxEndedEvent(sagaAgentCtx *sagactx.SagaAgentContext, parentTxId string, compensationMethod string) (bool, error) { 207 | e := &saga_grpc.GrpcTxEvent{ 208 | ServiceName: c.serviceConfig.ServiceName, 209 | InstanceId: c.serviceConfig.InstanceId, 210 | Timestamp: time.Now().UnixNano() / int64(time.Millisecond), 211 | GlobalTxId: sagaAgentCtx.GlobalTxId, 212 | LocalTxId: sagaAgentCtx.LocalTxId, 213 | ParentTxId: parentTxId, 214 | Type: constants.EVENT_NAME_TXENDEDEVENT, 215 | Timeout: int32(0), 216 | CompensationMethod: compensationMethod, 217 | RetryMethod: "", 218 | Retries: 0, 219 | Payloads: nil, 220 | } 221 | ctx, cancel := context.WithTimeout(context.Background(), constants.GRPC_COMMUNICATE_TIMEOUT) 222 | defer cancel() 223 | grpcAck, err := c.txEventServiceClient.OnTxEvent(ctx, e) 224 | if err != nil { 225 | return false, err 226 | } 227 | return grpcAck.Aborted, nil 228 | } 229 | 230 | func (c *TransportContractor) SendTxAbortedEvent(sagaAgentCtx *sagactx.SagaAgentContext, parentTxId string, compensationMethod string, err error) (bool, error) { 231 | errStr := err.Error() 232 | if len(errStr) > constants.PAYLOADS_MAX_LENGTH { 233 | errStr = errStr[0:constants.PAYLOADS_MAX_LENGTH] 234 | } 235 | e := &saga_grpc.GrpcTxEvent{ 236 | ServiceName: c.serviceConfig.ServiceName, 237 | InstanceId: c.serviceConfig.InstanceId, 238 | Timestamp: time.Now().UnixNano() / int64(time.Millisecond), 239 | GlobalTxId: sagaAgentCtx.GlobalTxId, 240 | LocalTxId: sagaAgentCtx.LocalTxId, 241 | ParentTxId: parentTxId, 242 | Type: constants.EVENT_NAME_TXABORTEDEVENT, 243 | Timeout: int32(0), 244 | CompensationMethod: compensationMethod, 245 | RetryMethod: "", 246 | Retries: 0, 247 | Payloads: []byte(errStr), 248 | } 249 | ctx, cancel := context.WithTimeout(context.Background(), constants.GRPC_COMMUNICATE_TIMEOUT) 250 | defer cancel() 251 | grpcAck, err := c.txEventServiceClient.OnTxEvent(ctx, e) 252 | if err != nil { 253 | return false, err 254 | } 255 | return grpcAck.Aborted, nil 256 | } 257 | 258 | func (c *TransportContractor) scheduleProcessReconnectTask() { 259 | for { 260 | select { 261 | case taskFn := <- c.pendingTasks: 262 | taskFn() 263 | case <- time.Tick(constants.GRPC_RECONNECT_DELAY): 264 | continue 265 | } 266 | } 267 | } 268 | 269 | func (c *TransportContractor) reconnectTask(){ 270 | grpcServiceConfig := &saga_grpc.GrpcServiceConfig{ 271 | ServiceName: c.serviceConfig.ServiceName, 272 | InstanceId: c.serviceConfig.InstanceId, 273 | } 274 | c.logger.LogInfo(fmt.Sprintf("Retry connecting to coordinator at %s", c.coordinatorAddress)) 275 | ctx, cancel := context.WithTimeout(context.Background(), constants.GRPC_COMMUNICATE_TIMEOUT) 276 | defer cancel() 277 | c.txEventServiceClient.OnDisconnected(ctx, grpcServiceConfig) 278 | var err error 279 | err = c.initClient() 280 | if err != nil { 281 | c.logger.LogError(fmt.Sprintf("Failed to reconnect to coordinator at %s, error: %v", c.coordinatorAddress, err)) 282 | // create reconnect task 283 | c.pendingTasks <- c.reconnectTask 284 | return 285 | } else { 286 | c.logger.LogInfo(fmt.Sprintf("Retry connecting to coordinator at %s is successful", c.coordinatorAddress)) 287 | return 288 | } 289 | c.onConnectedClient, err = c.txEventServiceClient.OnConnected(context.Background(), grpcServiceConfig) 290 | if err != nil { 291 | c.logger.LogError(fmt.Sprintf("Failed to reconnect to coordinator at %s, error: %v", c.coordinatorAddress, err)) 292 | // create reconnect task 293 | c.pendingTasks <- c.reconnectTask 294 | return 295 | } else { 296 | c.logger.LogInfo(fmt.Sprintf("Retry connecting to coordinator at %s is successful", c.coordinatorAddress)) 297 | return 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /utils/devutils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "os" 4 | 5 | func DisableHttpProxy() { 6 | os.Setenv("http_proxy", "") 7 | os.Setenv("https_proxy", "") 8 | } 9 | -------------------------------------------------------------------------------- /utils/netutils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net" 5 | "errors" 6 | ) 7 | 8 | func GetFirstNotLoopbackIPv4Address() (string, error) { 9 | ifaces, err := net.Interfaces() 10 | if err != nil { 11 | return "", err 12 | } 13 | for _, iface := range ifaces { 14 | if (iface.Flags&net.FlagUp == net.FlagUp) && (iface.Flags&net.FlagLoopback != net.FlagLoopback) && (iface.Flags&net.FlagPointToPoint != net.FlagPointToPoint) { 15 | addrs, err := iface.Addrs() 16 | if err != nil { 17 | return "", err 18 | } 19 | for _, addr := range addrs { 20 | if ipAddr, ok := addr.(*net.IPNet); ok { 21 | ipV4 := ipAddr.IP.To4() 22 | if ipV4 != nil { 23 | return ipV4.String(), nil 24 | } 25 | } 26 | 27 | if ipAddr, ok := addr.(*net.IPAddr); ok { 28 | ipV4 := ipAddr.IP.To4() 29 | if ipV4 != nil { 30 | return ipV4.String(), nil 31 | } 32 | } 33 | } 34 | } 35 | } 36 | return "", errors.New("can not found not loopback IPv4 address") 37 | } 38 | -------------------------------------------------------------------------------- /utils/reflectutils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "reflect" 5 | "runtime" 6 | ) 7 | 8 | func GetFnName(fn interface{}) string { 9 | compensedFunc := reflect.ValueOf(fn) 10 | fnName := runtime.FuncForPC(compensedFunc.Pointer()).Name() 11 | return fnName 12 | } 13 | --------------------------------------------------------------------------------