This is an automated email from the ASF dual-hosted git repository.
zfeng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-seata-go-samples.git
The following commit(s) were added to refs/heads/main by this push:
new 78912f7 feat: tcc integrate (#75)
78912f7 is described below
commit 78912f7b635923593ed55d36260c2e354e026eb7
Author: 马荣贺(Logan) <[email protected]>
AuthorDate: Sat Dec 6 08:17:24 2025 +0800
feat: tcc integrate (#75)
Co-authored-by: maronghe <[email protected]>
---
integrate_test/tcc/insert/Makefile | 21 ++++
integrate_test/tcc/insert/main.go | 174 +++++++++++++++++++++++++++
integrate_test/tcc/insert_on_update/Makefile | 21 ++++
integrate_test/tcc/insert_on_update/main.go | 153 +++++++++++++++++++++++
integrate_test/tcc/select_on_update/Makefile | 21 ++++
integrate_test/tcc/select_on_update/main.go | 134 +++++++++++++++++++++
start_integrate_test.sh | 4 +-
7 files changed, 527 insertions(+), 1 deletion(-)
diff --git a/integrate_test/tcc/insert/Makefile
b/integrate_test/tcc/insert/Makefile
new file mode 100644
index 0000000..bce2f91
--- /dev/null
+++ b/integrate_test/tcc/insert/Makefile
@@ -0,0 +1,21 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+
+
+run:
+ go run $(DIRECTORY)/main.go
diff --git a/integrate_test/tcc/insert/main.go
b/integrate_test/tcc/insert/main.go
new file mode 100644
index 0000000..02f780e
--- /dev/null
+++ b/integrate_test/tcc/insert/main.go
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package main
+
+import (
+ "context"
+ "database/sql"
+ "errors"
+ "log"
+
+ "gorm.io/driver/mysql"
+ "gorm.io/gorm"
+
+ "seata.apache.org/seata-go/pkg/client"
+ "seata.apache.org/seata-go/pkg/rm/tcc"
+ "seata.apache.org/seata-go/pkg/tm"
+)
+
+type OrderTblModel struct {
+ Id int64 `gorm:"column:id;primaryKey"`
+ UserId string `gorm:"column:user_id"`
+ CommodityCode string `gorm:"column:commodity_code"`
+ Count int64 `gorm:"column:count"`
+ Money int64 `gorm:"column:money"`
+ Descs string `gorm:"column:descs"`
+}
+
+var gormDB *gorm.DB
+
+type OrderTCCService struct{}
+
+func (o *OrderTCCService) GetActionName() string {
+ return "OrderTCCService"
+}
+
+func (o *OrderTCCService) Prepare(ctx context.Context, params interface{})
(bool, error) {
+ order := params.(OrderTblModel)
+ if err :=
gormDB.WithContext(ctx).Table("order_tbl").Create(&order).Error; err != nil {
+ return false, err
+ }
+ log.Printf("[Prepare] insert order %+v", order)
+ return true, nil
+}
+
+func (o *OrderTCCService) Commit(ctx context.Context, bac
*tm.BusinessActionContext) (bool, error) {
+ log.Printf("[Commit] confirm order")
+ return true, nil
+}
+
+func (o *OrderTCCService) Rollback(ctx context.Context, bac
*tm.BusinessActionContext) (bool, error) {
+ if err := gormDB.WithContext(ctx).
+ Table("order_tbl").
+ Where("user_id = ? AND commodity_code = ?", "U10001", "C10001").
+ Delete(nil).Error; err != nil {
+ return false, err
+ }
+ log.Printf("[Rollback] cancel order")
+ return true, nil
+}
+
+func initDB() {
+ sqlDB, err := sql.Open("mysql",
"root:12345678@tcp(127.0.0.1:3307)/seata_client?parseTime=true")
+ if err != nil {
+ panic(err)
+ }
+ gormDB, err = gorm.Open(mysql.New(mysql.Config{Conn: sqlDB}),
&gorm.Config{})
+ if err != nil {
+ panic(err)
+ }
+}
+
+func initConfig() {
+ client.InitPath("conf/seatago.yml")
+ initDB()
+}
+
+func main() {
+ initConfig()
+ ctx := context.Background()
+
+ tccServiceProxy, err := tcc.NewTCCServiceProxy(&OrderTCCService{})
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // ---------------- Insert ----------------
+ order := getData()
+
+ err = tm.WithGlobalTx(ctx, &tm.GtxConfig{Name: "TCC_Insert"},
func(txCtx context.Context) error {
+ ok, err := tccServiceProxy.Prepare(txCtx, order)
+ if err != nil {
+ return err
+ }
+ if okBool, ok := ok.(bool); !ok || !okBool {
+ return errors.New("prepare insert failed")
+ }
+ return nil
+ })
+ if err != nil {
+ log.Fatalf("insert transaction failed: %v", err)
+ }
+ log.Println("Insert success")
+
+ // ---------------- Read ----------------
+ var result OrderTblModel
+ if err := gormDB.WithContext(ctx).
+ Table("order_tbl").
+ Where("user_id = ? AND commodity_code = ?", order.UserId,
order.CommodityCode).
+ First(&result).Error; err != nil {
+ log.Fatal(err)
+ }
+ log.Printf("Read success Found: %+v", result)
+
+ // ---------------- Update ----------------
+ err = tm.WithGlobalTx(ctx, &tm.GtxConfig{Name: "TCC_Update"},
func(txCtx context.Context) error {
+ if err := gormDB.WithContext(txCtx).
+ Table("order_tbl").
+ Where("id = ?", result.Id).
+ Update("descs", "TCC update test").Error; err != nil {
+ return err
+ }
+ log.Printf("[Update] id=%d descs=updated", result.Id)
+ return nil
+ })
+ if err != nil {
+ log.Fatalf("update transaction failed: %v", err)
+ }
+ log.Println("Update success")
+
+ // ---------------- Delete ----------------
+ err = tm.WithGlobalTx(ctx, &tm.GtxConfig{Name: "TCC_Delete"},
func(txCtx context.Context) error {
+ if err := gormDB.WithContext(txCtx).
+ Table("order_tbl").
+ Where("id = ?", result.Id).
+ Delete(nil).Error; err != nil {
+ return err
+ }
+ log.Printf("[Delete] id=%d", result.Id)
+ return nil
+ })
+ if err != nil {
+ log.Fatalf("delete transaction failed: %v", err)
+ }
+ log.Println("Delete success")
+
+ log.Println("TCC CRUD integration test passed! 🎉")
+
+}
+
+func getData() OrderTblModel {
+ return OrderTblModel{
+ Id: 20001,
+ UserId: "NO-100003",
+ CommodityCode: "C100001",
+ Count: 1,
+ Money: 50,
+ Descs: "TCC insert test",
+ }
+}
diff --git a/integrate_test/tcc/insert_on_update/Makefile
b/integrate_test/tcc/insert_on_update/Makefile
new file mode 100644
index 0000000..bce2f91
--- /dev/null
+++ b/integrate_test/tcc/insert_on_update/Makefile
@@ -0,0 +1,21 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+
+
+run:
+ go run $(DIRECTORY)/main.go
diff --git a/integrate_test/tcc/insert_on_update/main.go
b/integrate_test/tcc/insert_on_update/main.go
new file mode 100644
index 0000000..4dfa60d
--- /dev/null
+++ b/integrate_test/tcc/insert_on_update/main.go
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package main
+
+import (
+ "context"
+ "database/sql"
+ "errors"
+ "log"
+
+ "gorm.io/driver/mysql"
+ "gorm.io/gorm"
+ "gorm.io/gorm/clause"
+
+ "seata.apache.org/seata-go/pkg/client"
+ "seata.apache.org/seata-go/pkg/rm/tcc"
+ "seata.apache.org/seata-go/pkg/tm"
+)
+
+type OrderTblModel struct {
+ Id int64 `gorm:"column:id;primaryKey"`
+ UserId string `gorm:"column:user_id"`
+ CommodityCode string `gorm:"column:commodity_code"`
+ Count int64 `gorm:"column:count"`
+ Money int64 `gorm:"column:money"`
+ Descs string `gorm:"column:descs"`
+}
+
+var gormDB *gorm.DB
+
+type TCCInsertOnUpdateService struct{}
+
+func (t *TCCInsertOnUpdateService) Prepare(ctx context.Context, params
interface{}) (bool, error) {
+ order := params.(OrderTblModel)
+
+ // Insert on update operation using GORM
+ err :=
gormDB.WithContext(ctx).Table("order_tbl").Clauses(clause.OnConflict{
+ Columns: []clause.Column{{Name: "id"}}, // by primary key
+ DoUpdates: clause.Assignments(map[string]interface{}{"descs":
order.Descs}),
+ }).Create(&order).Error
+
+ if err != nil {
+ return false, err
+ }
+ log.Printf("[Prepare] insert on update order %+v", order)
+ return true, nil
+}
+
+func (t *TCCInsertOnUpdateService) Commit(ctx context.Context,
businessActionContext *tm.BusinessActionContext) (bool, error) {
+ log.Printf("[Commit] confirm insert on update order")
+ return true, nil
+}
+
+func (t *TCCInsertOnUpdateService) Rollback(ctx context.Context,
businessActionContext *tm.BusinessActionContext) (bool, error) {
+ log.Printf("[Rollback] cancel insert on update")
+
+ // Delete the record that was inserted/updated in Prepare phase
+ // Using the same ID that was used in the test
+ if err := gormDB.WithContext(ctx).
+ Table("order_tbl").
+ Where("id = ?", 1). // The test uses ID=1
+ Delete(nil).Error; err != nil {
+ return false, err
+ }
+ log.Printf("[Rollback] deleted order with id=1")
+ return true, nil
+}
+
+func (t *TCCInsertOnUpdateService) GetActionName() string {
+ return "TCCInsertOnUpdateService"
+}
+
+func initDB() {
+ sqlDB, err := sql.Open("mysql",
"root:12345678@tcp(127.0.0.1:3307)/seata_client?parseTime=true")
+ if err != nil {
+ panic(err)
+ }
+ gormDB, err = gorm.Open(mysql.New(mysql.Config{Conn: sqlDB}),
&gorm.Config{})
+ if err != nil {
+ panic(err)
+ }
+}
+
+func initConfig() {
+ client.InitPath("conf/seatago.yml")
+ initDB()
+}
+
+func main() {
+ initConfig()
+ ctx := context.Background()
+
+ tccServiceProxy, err :=
tcc.NewTCCServiceProxy(&TCCInsertOnUpdateService{})
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // ---------------- Insert On Update ----------------
+ order := getData()
+
+ err = tm.WithGlobalTx(ctx, &tm.GtxConfig{Name: "TCC_InsertOnUpdate"},
func(txCtx context.Context) error {
+ ok, err := tccServiceProxy.Prepare(txCtx, order)
+ if err != nil {
+ return err
+ }
+ if okBool, ok := ok.(bool); !ok || !okBool {
+ return errors.New("prepare insert on update failed")
+ }
+ return nil
+ })
+ if err != nil {
+ log.Fatalf("insert on update transaction failed: %v", err)
+ }
+ log.Println("Insert on update success")
+
+ // ---------------- Verify ----------------
+ var result OrderTblModel
+ if err := gormDB.WithContext(ctx).
+ Table("order_tbl").
+ Where("id = ?", order.Id).
+ First(&result).Error; err != nil {
+ log.Fatal(err)
+ }
+ log.Printf("Verify success Found: %+v", result)
+
+ log.Println("TCC insert on update integration test passed! 🎉")
+}
+
+func getData() OrderTblModel {
+ return OrderTblModel{
+ Id: 1,
+ UserId: "NO-100003",
+ CommodityCode: "C100001",
+ Count: 101,
+ Money: 11,
+ Descs: "TCC insert on update test",
+ }
+}
diff --git a/integrate_test/tcc/select_on_update/Makefile
b/integrate_test/tcc/select_on_update/Makefile
new file mode 100644
index 0000000..bce2f91
--- /dev/null
+++ b/integrate_test/tcc/select_on_update/Makefile
@@ -0,0 +1,21 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+
+
+run:
+ go run $(DIRECTORY)/main.go
diff --git a/integrate_test/tcc/select_on_update/main.go
b/integrate_test/tcc/select_on_update/main.go
new file mode 100644
index 0000000..f569910
--- /dev/null
+++ b/integrate_test/tcc/select_on_update/main.go
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package main
+
+import (
+ "context"
+ "database/sql"
+ "errors"
+ "log"
+
+ "gorm.io/driver/mysql"
+ "gorm.io/gorm"
+ "gorm.io/gorm/clause"
+
+ "seata.apache.org/seata-go/pkg/client"
+ "seata.apache.org/seata-go/pkg/rm/tcc"
+ "seata.apache.org/seata-go/pkg/tm"
+)
+
+type OrderTblModel struct {
+ Id int64 `gorm:"column:id;primaryKey"`
+ UserId string `gorm:"column:user_id"`
+ CommodityCode string `gorm:"column:commodity_code"`
+ Count int64 `gorm:"column:count"`
+ Money int64 `gorm:"column:money"`
+ Descs string `gorm:"column:descs"`
+}
+
+var gormDB *gorm.DB
+
+type TCCSelectForUpdateService struct{}
+
+func (t *TCCSelectForUpdateService) Prepare(ctx context.Context, params
interface{}) (bool, error) {
+ queryParams := params.(map[string]interface{})
+ userId := queryParams["userId"].(string)
+ commodityCode := queryParams["commodityCode"].(string)
+
+ // Select for update operation using GORM
+ var order OrderTblModel
+ err := gormDB.WithContext(ctx).Table("order_tbl").
+ Where("user_id = ? AND commodity_code = ?", userId,
commodityCode).
+ Clauses(clause.Locking{Strength: "UPDATE"}).
+ First(&order).Error
+
+ if err != nil {
+ return false, err
+ }
+ log.Printf("[Prepare] select for update found order %+v", order)
+ return true, nil
+}
+
+func (t *TCCSelectForUpdateService) Commit(ctx context.Context,
businessActionContext *tm.BusinessActionContext) (bool, error) {
+ log.Printf("[Commit] confirm select for update")
+ return true, nil
+}
+
+func (t *TCCSelectForUpdateService) Rollback(ctx context.Context,
businessActionContext *tm.BusinessActionContext) (bool, error) {
+ log.Printf("[Rollback] cancel select for update")
+ // Select for update doesn't modify data, just releases the lock
+ // The database will automatically release the lock when transaction
ends
+ return true, nil
+}
+
+func (t *TCCSelectForUpdateService) GetActionName() string {
+ return "TCCSelectForUpdateService"
+}
+
+func initDB() {
+ sqlDB, err := sql.Open("mysql",
"root:12345678@tcp(127.0.0.1:3307)/seata_client?parseTime=true")
+ if err != nil {
+ panic(err)
+ }
+ gormDB, err = gorm.Open(mysql.New(mysql.Config{Conn: sqlDB}),
&gorm.Config{})
+ if err != nil {
+ panic(err)
+ }
+}
+
+func initConfig() {
+ client.InitPath("conf/seatago.yml")
+ initDB()
+}
+
+func main() {
+ initConfig()
+ ctx := context.Background()
+
+ tccServiceProxy, err :=
tcc.NewTCCServiceProxy(&TCCSelectForUpdateService{})
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // ---------------- Select For Update ----------------
+ queryParams := getData()
+
+ err = tm.WithGlobalTx(ctx, &tm.GtxConfig{Name: "TCC_SelectForUpdate"},
func(txCtx context.Context) error {
+ ok, err := tccServiceProxy.Prepare(txCtx, queryParams)
+ if err != nil {
+ return err
+ }
+ if okBool, ok := ok.(bool); !ok || !okBool {
+ return errors.New("prepare select for update failed")
+ }
+ return nil
+ })
+ if err != nil {
+ log.Fatalf("select for update transaction failed: %v", err)
+ }
+ log.Println("Select for update success")
+
+ log.Println("TCC select for update integration test passed! 🎉")
+}
+
+func getData() map[string]interface{} {
+ return map[string]interface{}{
+ "userId": "NO-100001",
+ "commodityCode": "C100000",
+ }
+}
diff --git a/start_integrate_test.sh b/start_integrate_test.sh
index 76415df..9cc7aa8 100755
--- a/start_integrate_test.sh
+++ b/start_integrate_test.sh
@@ -18,7 +18,9 @@
# tests
array+=("integrate_test/at/insert")
-
+array+=("integrate_test/tcc/insert")
+array+=("integrate_test/tcc/insert_on_update")
+array+=("integrate_test/tcc/select_on_update")
DOCKER_DIR=$(pwd)/dockercompose
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]