You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@seata.apache.org by zf...@apache.org on 2025/12/06 00:17:27 UTC

(incubator-seata-go-samples) branch main updated: feat: tcc integrate (#75)

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) <lo...@gmail.com>
AuthorDate: Sat Dec 6 08:17:24 2025 +0800

    feat: tcc integrate (#75)
    
    Co-authored-by: maronghe <ma...@zhihu.com>
---
 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: notifications-unsubscribe@seata.apache.org
For additional commands, e-mail: notifications-help@seata.apache.org