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]

Reply via email to