Well, Go Design Pattern Combat series, a golang series of design pattern business real use.

preface

This series focuses on how to use design patterns in our real business scenarios.

This series of articles mainly adopts the following structure:

  • What is “XX design pattern”?
  • What real business scenarios can use the “XX design pattern”?
  • How to use “XX design pattern”?

This article focuses on how “composite pattern” can be used in real business scenarios combined with the natural concurrency features of the Go language.

Previous articles of the code components | Go design patterns of actual combat, have introduced the concept of “portfolio model”, and its use in business. Today we are upgrading the “composite mode” to “concurrent composite mode” in combination with the natural concurrency features of the Go language.

Let’s review the simple “combination mode” of knowledge, can view the article in detail the code components | Go design patterns of actual combat”

What is “concurrent composition mode”?

The concept of composite patterns:

A hierarchical object consists of a series of objects that have parent-child relationships through a tree structure.

Concepts of concurrent composition patterns:

A hierarchical object consists of a series of objects that have a parent-child relationship through a tree structure. The children can be executed sequentially or concurrently

Advantages of concurrent composite mode:

  • Serial services (blocking parts, such as network IO) can be executed concurrently to take advantage of multiple cores to improve performance.

What real business scenarios can use the “concurrent composition pattern”?

Using the “order settlement page” in “Combination Mode” as an example, let’s continue to look at the order settlement page of a certain east:

From the display form of the page, we can see:

  • A page consists of several modules, such as:
    • Address module: obtain user address data
    • Payment method module: Get the list of payment methods
    • Store module: obtain information about stores, shopping cart selected goods and so on
    • Invoice module: Get the list of invoice types
    • Coupon module: get user coupon list
    • A bean module: obtain user integral information
    • Gift card module: Obtain the gift card list
    • Order detail amount module: Obtain order amount information
  • A single module can consist of multiple sub-modules
    • The store module is composed of the following modules:
      • Commodity module: get the information of the goods selected in the shopping cart
      • After-sale module: obtain after-sale information of goods
      • Preferential module: obtain the information of preferential activities in which the product participates
      • Logistics module: get the list of distribution methods supported by goods

Execute the process according to the business logic of “composite mode” :

However, it is clear that some modules do not depend on each other and involve blocking operations such as remote service calls, such as:

  • When the address module calls the address service to obtain the user’s address data.
  • Payment mode module can also read Redis to get payment mode list data and so on.

So: some modules can actually be executed concurrently.

If we change the above non-dependent module to concurrent execution, we get the following execution flow:

How to use “concurrent combination mode”?

Modeling process of “concurrent combination model” entirely can refer to previous articles of the code components | Go design patterns of actual combat “, we just need to focus on here.

The core of the concurrent composite mode is the Component interface. Let’s look at the Component interface of the composite mode (optimized in the previous article to further encapsulate the BusinessLogicDo method) :

// Component interface
type Component interface {
	// Add a child componentMount(c Component, components ... Component) error// Remove a child component
	Remove(c Component) error
	// Execute the current component business and execute the child components
	// CTX business context
	// currentConponent Indicates the current component
	Do(ctx *Context, currentConponent Component) error
	// Execute the current component business logic
	BusinessLogicDo(ctx *Context) error
	// Execute the child component
	ChildsDo(ctx *Context) error
}
Copy the code

Take a look at the Component interface for concurrent composite mode, which looks like this:

// Component interface
type Component interface {
	// Add a child componentMount(c Component, components ... Component) error// Remove a child component
	Remove(c Component) error
	// Execute the current component business: 'BusinessLogicDo' and the child component: 'ChildsDo'
	// CTX business context
	// currentConponent Indicates the current component
	// The WaitGroup object of the WG parent component
	// Difference 1: The WaitGroup object parameter is added to wait for the child component's execution to complete.
	Do(ctx *Context, currentConponent Component, wg *sync.WaitGroup) error
	// Execute the current component business logic
	// resChan writes back to the channel of the current component business execution result
	// Difference 2: a channel parameter is added. The purpose is to introduce a timeout mechanism when the concurrent component executes logic. A channel is required to receive the execution result of the component
	BusinessLogicDo(resChan chan interface{}) error
	// Execute the child component
	ChildsDo(ctx *Context) error
}
Copy the code

Let’s take a closer look at the following points when introducing concurrency as opposed to “composite mode” :

  • And send child components need to set timeout: prevent child components execution time is too long, solution keywordcontext.WithTimeout
  • Distinguish between common components and concurrent components: composite base components, packaged as concurrent base components
  • A parent component that owns and issues child components needs to wait for and issues child components to complete execution (including timeouts), solution keywordsync.WaitGroup
  • If the sub-components are sent to execute their own service logic, timeout detection is required: Prevents the sub-components from executing their own service logic for a long timeselectand<-ctx.Done()

The first point is that the timeout is required to send the child component

// Context Business Context
type Context struct {
	// context.WithTimeout Derived subcontext
	TimeoutCtx context.Context
	// The timeout function
	context.CancelFunc
}
Copy the code

Second point: Distinguish between normal components and concurrent components

Add a new concurrency BaseComponent structure BaseConcurrencyComponent and synthesize and reuse the BaseComponent BaseComponent in composite mode as follows:

BaseConcurrencyComponent Concurrency base component
type BaseConcurrencyComponent struct {
	// Composite base components
	BaseComponent
	// Whether the current component has and sends child components
	HasChildConcurrencyComponents bool
	// And send a list of child components
	ChildConcurrencyComponents []Component
	/ / wg object
	*sync.WaitGroup
	// Current component business execution result channel
	logicResChan chan interface{}
	// Error message during current component execution
	Err error
}
Copy the code

Third: A parent that owns and issues child components needs to wait and issue child components to complete execution (including timeouts)

Modify the ChildsDo method in “composite mode” to support concurrent execution of subcomponents. The main changes and implementation are as follows:

  • throughgoKeyword execution subcomponent
  • through*WaitGroup.Wait()Wait for the results of child component execution
// ChildsDo executes the child component
func (bc *BaseConcurrencyComponent) ChildsDo(ctx *Context) (err error) {
	if bc.WaitGroup == nil {
		bc.WaitGroup = &sync.WaitGroup{}
	}
	// Execute and send the child component
	for _, childComponent := range bc.ChildConcurrencyComponents {
		bc.WaitGroup.Add(1)
		go childComponent.Do(ctx, childComponent, bc.WaitGroup)
	}
	// Execute the child component
	for _, childComponent := range bc.ChildComponents {
		if err = childComponent.Do(ctx, childComponent, nil); err ! =nil {
			return err
		}
	}
	if bc.HasChildConcurrencyComponents {
		// Wait for the result of concurrent component execution
		bc.WaitGroup.Wait()
	}
	return
}
Copy the code

Fourth point: sending a child component to execute its own business logic requires timeout detection

The channel returned by the Done() subcontext derived from the select context.withTimeout () keyword, which will be closed if timeout occurs. The specific implementation code is as follows:

// Do executes the child component
// CTX business context
// currentConponent Indicates the current component
// The WaitGroup object of the WG parent component
func (bc *BaseConcurrencyComponent) Do(ctx *Context, currentConponent Component, wg *sync.WaitGroup) (err error) {
	defer wg.Done()
	// Initialize and issue the child component channel
	if bc.logicResChan == nil {
		bc.logicResChan = make(chan interface{}, 1)}go currentConponent.BusinessLogicDo(bc.logicResChan)

	select {
	// Wait for the service execution result
	case <-bc.logicResChan:
		// Service execution result
		fmt.Println(runFuncName(), "bc.BusinessLogicDo wait.done...")
		break
	// Wait for timeout
	case <-ctx.TimeoutCtx.Done():
		// Exit due to timeout
		fmt.Println(runFuncName(), "bc.BusinessLogicDo timeout...")
		bc.Err = ErrConcurrencyComponentTimeout
		break
	}
	// Execute the child component
	err = currentConponent.ChildsDo(ctx)
	return
}
Copy the code

The demo code

package main

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"reflect"
	"sync"
	"time"
)

//------------------------------------------------------------
//Go design mode combat series
// Combination mode
//@auhtor TIGERB<https://github.com/TIGERB>
//------------------------------------------------------------

//example:
// Create a root component
// If the child component has a concurrent component, the parent component must be a concurrent component
// type RootComponent struct {
// BaseConcurrencyComponent
// }
//
// func (bc *RootComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// // do nothing
// return
// }
//
// Create a concurrent component
// type DemoConcurrenyComponent struct {
// BaseConcurrencyComponent
// }
//
// func (bc *DemoConcurrenyComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// // Concurrent component business logic is populated here
// return
// }
//
// Create a normal component
// type DemoComponent struct {
// BaseComponent
// }
//
// func (bc *DemoComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// // Common component business logic to populate here
// return
// }
//
// // Common Components
// root.Mount(
// &DemoComponent{},
// )
//
// // Concurrent Component
// root := &RootComponent{}
// root.MountConcurrency(
// &DemoConcurrenyComponent{},
// )
//
// // Initializes the service context and sets the timeout period
// ctx := GetContext(5 * time.Second)
// defer ctx.CancelFunc()
// // Starts executing child components
// root.ChildsDo(ctx)

var (
	/ / ErrConcurrencyComponentTimeout concurrent component business timeout
	ErrConcurrencyComponentTimeout = errors.New("Concurrency Component Timeout"))// Context Business Context
type Context struct {
	// context.WithTimeout Derived subcontext
	TimeoutCtx context.Context
	// The timeout function
	context.CancelFunc
}

// GetContext gets the business context instance
// d Timeout period
func GetContext(d time.Duration) *Context {
	c := &Context{}
	c.TimeoutCtx, c.CancelFunc = context.WithTimeout(context.Background(), d)
	return c
}

// Component interface
type Component interface {
	// Add a child componentMount(c Component, components ... Component) error// Remove a child component
	Remove(c Component) error
	// Execute the current component business: 'BusinessLogicDo' and the child component: 'ChildsDo'
	// CTX business context
	// currentConponent Indicates the current component
	// The WaitGroup object of the WG parent component
	Do(ctx *Context, currentConponent Component, wg *sync.WaitGroup) error
	// Execute the current component business logic
	// resChan writes back to the channel of the current component business execution result
	BusinessLogicDo(resChan chan interface{}) error
	// Execute the child component
	ChildsDo(ctx *Context) error
}

// BaseComponent BaseComponent
// Implement Add: Add a child component
// Implement Remove: Removes a child component
type BaseComponent struct {
	// List of child components
	ChildComponents []Component
}

// Mount mounts a child component
func (bc *BaseComponent) Mount(c Component, components ... Component) (err error) {
	bc.ChildComponents = append(bc.ChildComponents, c)
	if len(components) == 0 {
		return
	}
	bc.ChildComponents = append(bc.ChildComponents, components...)
	return
}

// Remove Removes a child component
func (bc *BaseComponent) Remove(c Component) (err error) {
	if len(bc.ChildComponents) == 0 {
		return
	}
	for k, childComponent := range bc.ChildComponents {
		if c == childComponent {
			fmt.Println(runFuncName(), "Remove.", reflect.TypeOf(childComponent))
			bc.ChildComponents = append(bc.ChildComponents[:k], bc.ChildComponents[k+1:]...)
		}
	}
	return
}

// Do executes the child component
// CTX business context
// currentConponent Indicates the current component
// The WaitGroup object of the WG parent component
func (bc *BaseComponent) Do(ctx *Context, currentConponent Component, wg *sync.WaitGroup) (err error) {
	Execute the current component business code
	err = currentConponent.BusinessLogicDo(nil)
	iferr ! =nil {
		return err
	}
	// Execute the child component
	return currentConponent.ChildsDo(ctx)
}

// BusinessLogicDo where the current component business logic code is populated
func (bc *BaseComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
	// do nothing
	return
}

// ChildsDo executes the child component
func (bc *BaseComponent) ChildsDo(ctx *Context) (err error) {
	// Execute the child component
	for _, childComponent := range bc.ChildComponents {
		if err = childComponent.Do(ctx, childComponent, nil); err ! =nil {
			return err
		}
	}
	return
}

BaseConcurrencyComponent Concurrency base component
type BaseConcurrencyComponent struct {
	// Composite base components
	BaseComponent
	// Whether the current component has and sends child components
	HasChildConcurrencyComponents bool
	// And send a list of child components
	ChildConcurrencyComponents []Component
	/ / wg object
	*sync.WaitGroup
	// Current component business execution result channel
	logicResChan chan interface{}
	// Error message during current component execution
	Err error
}

// Remove Removes a child component
func (bc *BaseConcurrencyComponent) Remove(c Component) (err error) {
	if len(bc.ChildComponents) == 0 {
		return
	}
	for k, childComponent := range bc.ChildComponents {
		if c == childComponent {
			fmt.Println(runFuncName(), "Remove.", reflect.TypeOf(childComponent))
			bc.ChildComponents = append(bc.ChildComponents[:k], bc.ChildComponents[k+1:]...)
		}
	}
	for k, childComponent := range bc.ChildConcurrencyComponents {
		if c == childComponent {
			fmt.Println(runFuncName(), "Remove.", reflect.TypeOf(childComponent))
			bc.ChildConcurrencyComponents = append(bc.ChildComponents[:k], bc.ChildComponents[k+1:]...)
		}
	}
	return
}

// MountConcurrency mounts and sends a child component
func (bc *BaseConcurrencyComponent) MountConcurrency(c Component, components ... Component) (err error) {
	bc.HasChildConcurrencyComponents = true
	bc.ChildConcurrencyComponents = append(bc.ChildConcurrencyComponents, c)
	if len(components) == 0 {
		return
	}
	bc.ChildConcurrencyComponents = append(bc.ChildConcurrencyComponents, components...)
	return
}

// ChildsDo executes the child component
func (bc *BaseConcurrencyComponent) ChildsDo(ctx *Context) (err error) {
	if bc.WaitGroup == nil {
		bc.WaitGroup = &sync.WaitGroup{}
	}
	// Execute and send the child component
	for _, childComponent := range bc.ChildConcurrencyComponents {
		bc.WaitGroup.Add(1)
		go childComponent.Do(ctx, childComponent, bc.WaitGroup)
	}
	// Execute the child component
	for _, childComponent := range bc.ChildComponents {
		if err = childComponent.Do(ctx, childComponent, nil); err ! =nil {
			return err
		}
	}
	if bc.HasChildConcurrencyComponents {
		// Wait for the result of concurrent component execution
		bc.WaitGroup.Wait()
	}
	return
}

// Do executes the child component
// CTX business context
// currentConponent Indicates the current component
// The WaitGroup object of the WG parent component
func (bc *BaseConcurrencyComponent) Do(ctx *Context, currentConponent Component, wg *sync.WaitGroup) (err error) {
	defer wg.Done()
	// Initialize and issue the child component channel
	if bc.logicResChan == nil {
		bc.logicResChan = make(chan interface{}, 1)}go currentConponent.BusinessLogicDo(bc.logicResChan)

	select {
	// Wait for the service execution result
	case <-bc.logicResChan:
		// Service execution result
		fmt.Println(runFuncName(), "bc.BusinessLogicDo wait.done...")
		break
	// Wait for timeout
	case <-ctx.TimeoutCtx.Done():
		// Exit due to timeout
		fmt.Println(runFuncName(), "bc.BusinessLogicDo timeout...")
		bc.Err = ErrConcurrencyComponentTimeout
		break
	}
	// Execute the child component
	err = currentConponent.ChildsDo(ctx)
	return
}

// CheckoutPageComponent Order settlement page component
type CheckoutPageComponent struct {
	// Composite base components
	BaseConcurrencyComponent
}

// BusinessLogicDo where the current component business logic code is populated
func (bc *CheckoutPageComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
	// The business logic of the current component is written here
	fmt.Println(runFuncName(), "Order settlement page component...")
	return
}

// AddressComponent AddressComponent
type AddressComponent struct {
	// Composite base components
	BaseConcurrencyComponent
}

// Where BusinessLogicDo concurrency components actually populate the business logic
func (bc *AddressComponent) BusinessLogicDo(resChan chan interface{}) error {
	fmt.Println(runFuncName(), "Address component...")
	fmt.Println(runFuncName(), "Get address info ing...")

	// Simulate a remote invocation of the address service
	http.Get("http://example.com/")

	resChan <- struct{} {}// Write the service execution result
	fmt.Println(runFuncName(), "Get address info done...")
	return nil
}

// PayMethodComponent Payment method component
type PayMethodComponent struct {
	// Composite base components
	BaseConcurrencyComponent
}

// Where BusinessLogicDo concurrency components actually populate the business logic
func (bc *PayMethodComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
	// The business logic of the current component is written here
	fmt.Println(runFuncName(), "Payment method component...")
	fmt.Println(runFuncName(), "Get payment method ing...")
	// Simulate remote invocation of address service omitted
	resChan <- struct{}{}
	fmt.Println(runFuncName(), "Get payment method done...")
	return nil
}

// StoreComponent Store components
type StoreComponent struct {
	// Composite base components
	BaseComponent
}

// Where BusinessLogicDo concurrency components actually populate the business logic
func (bc *StoreComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
	// The business logic of the current component is written here
	fmt.Println(runFuncName(), "Store components...")
	return
}

// SkuComponent Commodity component
type SkuComponent struct {
	// Composite base components
	BaseComponent
}

// Where BusinessLogicDo concurrency components actually populate the business logic
func (bc *SkuComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
	// The business logic of the current component is written here
	fmt.Println(runFuncName(), "Commodity components...")
	return
}

// PromotionComponent Promo component
type PromotionComponent struct {
	// Composite base components
	BaseComponent
}

// Where BusinessLogicDo concurrency components actually populate the business logic
func (bc *PromotionComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
	// The business logic of the current component is written here
	fmt.Println(runFuncName(), "Promo component...")
	return
}

// ExpressComponent Indicates the logistics component
type ExpressComponent struct {
	// Composite base components
	BaseComponent
}

// Where BusinessLogicDo concurrency components actually populate the business logic
func (bc *ExpressComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
	// The business logic of the current component is written here
	fmt.Println(runFuncName(), "Logistics components...")
	return
}

// AftersaleComponent
type AftersaleComponent struct {
	// Composite base components
	BaseComponent
}

// Where BusinessLogicDo concurrency components actually populate the business logic
func (bc *AftersaleComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
	// The business logic of the current component is written here
	fmt.Println(runFuncName(), "Aftermarket components...")
	return
}

// InvoiceComponent invoicing component
type InvoiceComponent struct {
	// Composite base components
	BaseConcurrencyComponent
}

// Where BusinessLogicDo concurrency components actually populate the business logic
func (bc *InvoiceComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
	// The business logic of the current component is written here
	fmt.Println(runFuncName(), "Invoice component...")
	fmt.Println(runFuncName(), "Get invoice information ing...")
	// Simulate remote invocation of address service omitted
	resChan <- struct{} {}// Write the service execution result
	fmt.Println(runFuncName(), "Get invoice information done...")
	return
}

// CouponComponent CouponComponent
type CouponComponent struct {
	// Composite base components
	BaseConcurrencyComponent
}

// Where BusinessLogicDo concurrency components actually populate the business logic
func (bc *CouponComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
	// The business logic of the current component is written here
	fmt.Println(runFuncName(), "Coupon kit...")
	fmt.Println(runFuncName(), "Get the best coupon ing...")

	// Simulate a remote call to the coupon service
	http.Get("http://example.com/")

	// Write the service execution result
	resChan <- struct{}{}
	fmt.Println(runFuncName(), Get the best coupon done...)
	return
}

// GiftCardComponent GiftCardComponent
type GiftCardComponent struct {
	// Composite base components
	BaseConcurrencyComponent
}

// Where BusinessLogicDo concurrency components actually populate the business logic
func (bc *GiftCardComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
	// The business logic of the current component is written here
	fmt.Println(runFuncName(), "Gift card components...")
	fmt.Println(runFuncName(), "Get gift card info ing...")
	// Simulate remote invocation of address service omitted
	resChan <- struct{} {}// Write the service execution result
	fmt.Println(runFuncName(), "Get gift card info done...")
	return
}

// OrderComponent Order amount details component
type OrderComponent struct {
	// Composite base components
	BaseComponent
}

// BusinessLogicDo where the current component business logic code is populated
func (bc *OrderComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
	// The business logic of the current component is written here
	fmt.Println(runFuncName(), "Order amount details component...")
	return
}

/ / the Demo sample
func Demo(a) {
	// Initialize the large component of the order settlement page
	checkoutPage := &CheckoutPageComponent{}

	// Mount child components
	storeComponent := &StoreComponent{}
	skuComponent := &SkuComponent{}
	skuComponent.Mount(
		&PromotionComponent{},
		&AftersaleComponent{},
	)
	storeComponent.Mount(
		skuComponent,
		&ExpressComponent{},
	)

	// Mount components --

	// Common components
	checkoutPage.Mount(
		storeComponent,
		&OrderComponent{},
	)
	// Concurrent components
	checkoutPage.MountConcurrency(
		&AddressComponent{},
		&PayMethodComponent{},
		&InvoiceComponent{},
		&CouponComponent{},
		&GiftCardComponent{},
	)

	// Initialize the business context and set the timeout
	ctx := GetContext(5 * time.Second)
	defer ctx.CancelFunc()
	// Start building page component data
	checkoutPage.ChildsDo(ctx)
}

func main(a) {
	runtime.GOMAXPROCS(runtime.NumCPU() - 1)
	Demo()
}

// Get the name of the running function
func runFuncName(a) string {
	pc := make([]uintptr.1)
	runtime.Callers(2, pc)
	f := runtime.FuncForPC(pc[0])
	return f.Name()
	return ""
}


Copy the code

Code running results:

Running] go run ".. / easy - tips/go/patterns/composite/concurrency/composite - concurrency. Go "main. (* StoreComponent). BusinessLogicDo store component... Main.(*SkuComponent).BusinessLogicDo commodity component... Main.(*PromotionComponent).BusinessLogicDo offers component... Main.(*AftersaleComponent).BusinessLogicDo aftermarket components... Main.(*ExpressComponent).BusinessLogicDo Logistics component... Main.(*OrderComponent).BusinessLogicDo Order Amount details component... Main.(*PayMethodComponent).BusinessLogicDo Payment method component... Main.(*PayMethodComponent).BusinessLogicDo Gets the payment method ing... Main.(*InvoiceComponent).BusinessLogicDo Invoicing component... Main.(*InvoiceComponent).BusinessLogicDo get invoice information ing... Main.(*GiftCardComponent).BusinessLogicDo GiftCardComponent... Main.(*GiftCardComponent).BusinessLogicDo Get gift card information ing... Main.(*CouponComponent).BusinessLogicDo CouponComponent... Main.(*CouponComponent).BusinessLogicDo Get invoice information ing... Main.(*AddressComponent).BusinessLogicDo AddressComponent... Main.(*AddressComponent).BusinessLogicDo Get address information ing... Main.(*InvoiceComponent).BusinessLogicDo... main.(*BaseConcurrencyComponent).Do bc.BusinessLogicDo wait.done... main.(*BaseConcurrencyComponent).Do bc.BusinessLogicDo wait.done... Main.(*PayMethodComponent).BusinessLogicDo Get paid method done... Main.(*AddressComponent).BusinessLogicDo Get address done... main.(*BaseConcurrencyComponent).Do bc.BusinessLogicDo wait.done... Main.(*CouponComponent).BusinessLogicDo Get invoice info done... main.(*BaseConcurrencyComponent).Do bc.BusinessLogicDo wait.done... Main.(*GiftCardComponent).BusinessLogicDo Get gift card info done... main.(*BaseConcurrencyComponent).Do bc.BusinessLogicDo wait.done...Copy the code

Benchmark comparison of Composite mode and Concurrent composite mode

Benchmark code:

package composite

import (
	"easy-tips/go/patterns/composite/concurrency"
	"easy-tips/go/patterns/composite/normal"
	"runtime"
	"testing"
)

// go test -benchmem -run=^$ easy-tips/go/patterns/composite -bench . -v -count=1 --benchtime 20s

func Benchmark_Normal(b *testing.B) {
	b.SetParallelism(runtime.NumCPU())
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			normal.Demo()
		}
	})
}

func Benchmark_Concurrency(b *testing.B) {
	b.SetParallelism(runtime.NumCPU())
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			concurrency.Demo()
		}
	})
}
Copy the code

Local machine Benchmark comparison test results:

(TIGERB) 🤔 ➜ composite git: (master) ✗ go test - benchmem - run = ^ $easy - tips/go/patterns/composite - bench. - v - count = 1 --benchtime 20s goos: darwin goarch: amd64 pkg: easy-tips/go/patterns/composite Benchmark_Normal-4 376 56666895 ns/op 35339 B/op 286 allocs/op Benchmark_Concurrency-4 32669301 ns/op 36445 B / 715 op 299 allocs/op PASS ok easy tips/go/patterns/composite 68.835 sCopy the code

Benchmark_Concurrency-4 has an average run time of 32669301 ns compared to Benchmark_Normal’s 56666895 ns.

conclusion

The “Concurrent composite pattern” is a “new pattern” formed by appropriate encapsulation of a specific design pattern combined with the inherent concurrency characteristics of the Go language.

Appendix basic code templates and usage instructions for concurrent composition patterns

//------------------------------------------------------------
//Go design mode combat series
// Combination mode
//@auhtor TIGERB<https://github.com/TIGERB>
//------------------------------------------------------------

//example:
// Create a root component
// If the child component has a concurrent component, the parent component must be a concurrent component
// type RootComponent struct {
// BaseConcurrencyComponent
// }
//
// func (bc *RootComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// // do nothing
// return
// }
//
// Create a concurrent component
// type DemoConcurrenyComponent struct {
// BaseConcurrencyComponent
// }
//
// func (bc *DemoConcurrenyComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// // Concurrent component business logic is populated here
// return
// }
//
// Create a normal component
// type DemoComponent struct {
// BaseComponent
// }
//
// func (bc *DemoComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// // Common component business logic to populate here
// return
// }
//
// // Common Components
// root.Mount(
// &DemoComponent{},
// )
//
// // Concurrent Component
// root := &RootComponent{}
// root.MountConcurrency(
// &DemoConcurrenyComponent{},
// )
//
// // Initializes the service context and sets the timeout period
// ctx := GetContext(5 * time.Second)
// defer ctx.CancelFunc()
// // Starts executing child components
// root.ChildsDo(ctx)

var (
	/ / ErrConcurrencyComponentTimeout concurrent component business timeout
	ErrConcurrencyComponentTimeout = errors.New("Concurrency Component Timeout"))// Context Business Context
type Context struct {
	// context.WithTimeout Derived subcontext
	TimeoutCtx context.Context
	// The timeout function
	context.CancelFunc
}

// GetContext gets the business context instance
// d Timeout period
func GetContext(d time.Duration) *Context {
	c := &Context{}
	c.TimeoutCtx, c.CancelFunc = context.WithTimeout(context.Background(), d)
	return c
}

// Component interface
type Component interface {
	// Add a child componentMount(c Component, components ... Component) error// Remove a child component
	Remove(c Component) error
	// Execute the current component business: 'BusinessLogicDo' and the child component: 'ChildsDo'
	// CTX business context
	// currentConponent Indicates the current component
	// The WaitGroup object of the WG parent component
	Do(ctx *Context, currentConponent Component, wg *sync.WaitGroup) error
	// Execute the current component business logic
	// resChan writes back to the channel of the current component business execution result
	BusinessLogicDo(resChan chan interface{}) error
	// Execute the child component
	ChildsDo(ctx *Context) error
}

// BaseComponent BaseComponent
// Implement Add: Add a child component
// Implement Remove: Removes a child component
type BaseComponent struct {
	// List of child components
	ChildComponents []Component
}

// Mount mounts a child component
func (bc *BaseComponent) Mount(c Component, components ... Component) (err error) {
	bc.ChildComponents = append(bc.ChildComponents, c)
	if len(components) == 0 {
		return
	}
	bc.ChildComponents = append(bc.ChildComponents, components...)
	return
}

// Remove Removes a child component
func (bc *BaseComponent) Remove(c Component) (err error) {
	if len(bc.ChildComponents) == 0 {
		return
	}
	for k, childComponent := range bc.ChildComponents {
		if c == childComponent {
			fmt.Println(runFuncName(), "Remove.", reflect.TypeOf(childComponent))
			bc.ChildComponents = append(bc.ChildComponents[:k], bc.ChildComponents[k+1:]...)
		}
	}
	return
}

// Do executes the child component
// CTX business context
// currentConponent Indicates the current component
// The WaitGroup object of the WG parent component
func (bc *BaseComponent) Do(ctx *Context, currentConponent Component, wg *sync.WaitGroup) (err error) {
	Execute the current component business code
	err = currentConponent.BusinessLogicDo(nil)
	iferr ! =nil {
		return err
	}
	// Execute the child component
	return currentConponent.ChildsDo(ctx)
}

// BusinessLogicDo where the current component business logic code is populated
func (bc *BaseComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
	// do nothing
	return
}

// ChildsDo executes the child component
func (bc *BaseComponent) ChildsDo(ctx *Context) (err error) {
	// Execute the child component
	for _, childComponent := range bc.ChildComponents {
		if err = childComponent.Do(ctx, childComponent, nil); err ! =nil {
			return err
		}
	}
	return
}

BaseConcurrencyComponent Concurrency base component
type BaseConcurrencyComponent struct {
	// Composite base components
	BaseComponent
	// Whether the current component has and sends child components
	HasChildConcurrencyComponents bool
	// And send a list of child components
	ChildConcurrencyComponents []Component
	/ / wg object
	*sync.WaitGroup
	// Current component business execution result channel
	logicResChan chan interface{}
	// Error message during current component execution
	Err error
}

// Remove Removes a child component
func (bc *BaseConcurrencyComponent) Remove(c Component) (err error) {
	if len(bc.ChildComponents) == 0 {
		return
	}
	for k, childComponent := range bc.ChildComponents {
		if c == childComponent {
			fmt.Println(runFuncName(), "Remove.", reflect.TypeOf(childComponent))
			bc.ChildComponents = append(bc.ChildComponents[:k], bc.ChildComponents[k+1:]...)
		}
	}
	for k, childComponent := range bc.ChildConcurrencyComponents {
		if c == childComponent {
			fmt.Println(runFuncName(), "Remove.", reflect.TypeOf(childComponent))
			bc.ChildConcurrencyComponents = append(bc.ChildComponents[:k], bc.ChildComponents[k+1:]...)
		}
	}
	return
}

// MountConcurrency mounts and sends a child component
func (bc *BaseConcurrencyComponent) MountConcurrency(c Component, components ... Component) (err error) {
	bc.HasChildConcurrencyComponents = true
	bc.ChildConcurrencyComponents = append(bc.ChildConcurrencyComponents, c)
	if len(components) == 0 {
		return
	}
	bc.ChildConcurrencyComponents = append(bc.ChildConcurrencyComponents, components...)
	return
}

// ChildsDo executes the child component
func (bc *BaseConcurrencyComponent) ChildsDo(ctx *Context) (err error) {
	if bc.WaitGroup == nil {
		bc.WaitGroup = &sync.WaitGroup{}
	}
	// Execute and send the child component
	for _, childComponent := range bc.ChildConcurrencyComponents {
		bc.WaitGroup.Add(1)
		go childComponent.Do(ctx, childComponent, bc.WaitGroup)
	}
	// Execute the child component
	for _, childComponent := range bc.ChildComponents {
		if err = childComponent.Do(ctx, childComponent, nil); err ! =nil {
			return err
		}
	}
	if bc.HasChildConcurrencyComponents {
		// Wait for the result of concurrent component execution
		bc.WaitGroup.Wait()
	}
	return
}

// Do executes the child component
// CTX business context
// currentConponent Indicates the current component
// The WaitGroup object of the WG parent component
func (bc *BaseConcurrencyComponent) Do(ctx *Context, currentConponent Component, wg *sync.WaitGroup) (err error) {
	defer wg.Done()
	// Initialize and issue the child component channel
	if bc.logicResChan == nil {
		bc.logicResChan = make(chan interface{}, 1)}go currentConponent.BusinessLogicDo(bc.logicResChan)

	select {
	// Wait for the service execution result
	case <-bc.logicResChan:
		// Service execution result
		fmt.Println(runFuncName(), "bc.BusinessLogicDo wait.done...")
		break
	// Wait for timeout
	case <-ctx.TimeoutCtx.Done():
		// Exit due to timeout
		fmt.Println(runFuncName(), "bc.BusinessLogicDo timeout...")
		bc.Err = ErrConcurrencyComponentTimeout
		break
	}
	// Execute the child component
	err = currentConponent.ChildsDo(ctx)
	return
}
Copy the code
Special note: the concept of some design patterns in this series may be different from the original concept, because it will be used in combination with the actual situation, take its essence, appropriate change, flexible use.Copy the code

The article lists

  • The code template | Go design patterns of actual combat
  • Chain called | Go design patterns of actual combat
  • Code components | Go design patterns of actual combat
  • Subscription notice | Go design patterns of actual combat
  • Customer decision | Go design patterns of actual combat
  • State transform | Go design patterns of actual combat

Go design pattern practice series for more articles click here to view