Event-driven architectures are relatively simple to understand, and it is generally accepted that good software architectures are decoupled and that microservices should not be coupled or dependent on each other. For example, when we call the function of the microservice go.srv.user-service in the code, we will find the address of the microservice through service discovery and then call it. Our code has direct call interaction with the microservice, which is not completely decoupled.

The source address

  • The source address
  • Comprehensive micro service project of Airentals

Publish and subscribe

To understand how event-driven architecture can completely decouple code, take a look at the publish and subscribe process of events. Microservice X notifies the messaging system that “X has completed” after completing task X. It does not care about which microservices are listening for this event and the impact of the event after it occurs. If an event occurs in the system, it is easy for other microservices to act accordingly.

For example, when a user-service creates a new user, email-service sends an email to the user indicating that the user has registered successfully, and message-service sends a short message to the website administrator notifying the user that the user has registered.

General implementation

After instantiating the other two micro-service clients in the user-service code, the function is called to send mail and SMS, and the code is highly coupled. The diagram below:

event-driven

In an event-driven architecture, a User-Service only needs to publish a topic “user.created” message to the messaging system, and two other services that subscribe to this topic can know that a user has registered, and when they get the user’s information, they can send an email or a text message. The diagram below:

Code implementation

Publish event Publish

package main

import (
	"context"
	"github.com/asim/go-micro/v3"
	"github.com/asim/go-micro/v3/metadata"
	"github.com/asim/go-micro/v3/server"
	"github.com/asim/go-micro/v3/util/log"
	proto "go-micro-examples/pubsub/proto"
)

// Sub All methods of Sub will be executed when
// a message is received
type Sub struct{}

// Process Method can be of any name
func (s *Sub) Process(ctx context.Context, event *proto.Event) error {
	md, _ := metadata.FromContext(ctx)
	log.Logf("[pubsub.1] Received event %+v with metadata %+v\n", event, md)
	// do something with event
	return nil
}

// Alternatively a function can be used
func subEv(ctx context.Context, event *proto.Event) error {
	md, _ := metadata.FromContext(ctx)
	log.Logf("[pubsub.2] Received event %+v with metadata %+v\n", event, md)
	// do something with event
	return nil
}

func main(a) {
	// create a service
	service := micro.NewService(
		micro.Name("go.micro.srv.pubsub"),// parse command line
	service.Init()

	// register subscriber
	if err := micro.RegisterSubscriber("example.topic,pubsub.1", service.Server(), new(Sub)); err ! =nil {
		log.Fatal(err)
	}

	// register subscriber with queue, each message is delivered to a unique subscriber
	if err := micro.RegisterSubscriber("example.topic.pubsub.2", service.Server(), subEv, server.SubscriberQueue("queue.pubsub")); err ! =nil {
		log.Fatal(err)
	}

	iferr := service.Run(); err ! =nil {
		log.Fatal(err)
	}
}
Copy the code

The running effect is as follows:

Example.top.pubsub. 1, Example.top.pubsub. 2

Subscribe Event Subscription

If events have subscriptions, then of course there will be subscriptions

package main

import (
	"context"
	"fmt"
	"github.com/asim/go-micro/v3"
	"github.com/asim/go-micro/v3/util/log"
	"github.com/pborman/uuid"
	proto "go-micro-examples/pubsub/proto"
	"time"
)

// send events using the publisher
func sendEv(topic string, p micro.Publisher) {
	t := time.NewTimer(time.Second)

	for _ = range t.C {
		// crate new event
		ev := &proto.Event{
			Id:        uuid.NewUUID().String(),
			Timestamp: time.Now().Unix(),
			Message:   fmt.Sprintf("Messaging you all day on %s", topic),
		}

		log.Logf("publishing %+v\n", ev)

		// publish an event
		iferr := p.Publish(context.Background(), ev); err ! =nil {
			log.Logf("error publishing %v", err)
		}
	}
}

func main(a) {
	// create a service
	service := micro.NewService(
		micro.Name("go.micro.cli.pubsub"),// parse command line
	service.Init()

	// create publisher
	pub1 := micro.NewEvent("example.topic.pubsub.1", service.Client())
	pub2 := micro.NewEvent("example.topic.pubsub.2", service.Client())

	// pub to topic 1
	go sendEv("example.topic.pubsub.1", pub1)
	// pub to topic 2
	go sendEv("example.topic.pubsub.2", pub2)

	// block forever
	select{}}Copy the code

The running effect is as follows:

As you can see from the figure, the subscription to the event succeeded and the invocation succeeded