servicemesh

package module
v0.0.0-...-92910d5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 20, 2023 License: MIT Imports: 11 Imported by: 76

README

About

This module provides an implementation of a "service mesh", which represents a collection of "services."

Included in this module are abstract interfaces for the service mesh and services (see the Mesh and Service interfaces), as well as other integration interfaces for logging, dependency injection, and graceful shutdown.

When using this module as a library for developing software, it necessitates the architecture of any given application be a composition, or mesh, of services. The mesh itself governs the lifecycle of the services, and therefore, the application as a whole.

At the highest level, typically in a main.go file, an application would look something like this:

func main() {
    app := servicemesh.New("My App")
	
    app.Add(&foo.Service{})
    app.Add(&bar.Service{})
    // add your other services here

    app.Run()
}

We can see that no particular service is responsible for invoking the run-loop of the service mesh; we invoke this run-loop one time in the main func of the application. We also dont manually assign any dependencies, or invoke the Init method of a service. This is all managed by the service mesh. This allows the mesh to perform dependency-injection, standard logger instantiation, and event-handler callback binding.

Examples

For examples see the examples repo.

Usage

This is the contract that all services must honor:

type Service interface {
    Init(mesh Mesh)
    Name() string
}

Here is a trivial example service:

// minimal service implementation
type fooService struct {}

func (s *fooService) Init(manager servicemesh.Mesh) {
	// Initialization logic for your service
}

func (s *fooService) Name() string {
	return "Foo"
}
// main.go would look like this
func main() {
    // Create the mesh instance
    mesh := servicemesh.New()
	
    // Add the service
    mesh.Add(&fooService{}) 
    
    // invoke the run-loop (blocking call)
    mesh.Run()
}

Adding Services

To add a service to the mesh, you need to create a struct that implements the Service interface. This interface requires the implementation of the Init() and Name() methods.

type Service interface {
	Init(mesh M)
	Name() string
}

The Init() method is called during the initialization phase of the service and allows you to perform any necessary setup. The Name() method returns the name of the service.

You can then add your service to the Manager using the Add() method:

mesh.Add(service)

Graceful Shutdown

The Manager supports graceful shutdown by listening for the interrupt signal (os.Interrupt). When the interrupt signal is received, the manager initiates the shutdown process and allows the services to perform cleanup operations. You can trigger the shutdown by pressing Ctrl+C in the console.

mesh.Run() // this is blocking until the interrupt fires

The Run() method blocks until the interrupt signal is received. Once the signal is received, the mesh calls the OnShutdown() method of each service, allowing them to perform any necessary cleanup. You can implement the cleanup logic within the OnShutdown() method of your service.

func (s *MyService) OnShutdown() {
	// Cleanup logic for your service
}

Logging Integration

The Manager integrates with the slog logging module to provide logging capabilities for your services. The manager automatically initializes a logger and passes it to the services that implement the HasLogger interface.

To use the logger within your service, you need to implement the HasLogger interface. The manager will invoke the SetLogger method automatically when the service is added to the mesh.

type HasLogger interface {
    Service
    SetLogger(logger *slog.Logger)
    Logger() *slog.Logger
}
func (s *MyService) SetLogger(logger *slog.Logger) {
	// Assign the logger to your service
	s.logger = logger
}

With the logger assigned, you can use it within your service to log messages:

myService.logger.Info("foo")

Interfaces

This module provides several interfaces that define the contracts for managing services within the mesh and implementing specific functionalities. These interfaces are designed to promote modularity and extensibility in your codebase.

Mesh

The Mesh interface describes the contract of the service mesh. The concrete implementation of this interface is defined in this module, but it is not exported. All you need to know about the Mesh, as a user of this module, is the following interface:

type Mesh interface {
    Add(Service) *sync.WaitGroup
    Remove(Service) *sync.WaitGroup
    Run()
    Shutdown() *sync.WaitGroup
    
	Services() []Service
    
	SetLogHandler(handler slog.Handler)
    SetLogLevel(level slog.Level)
    SetLogDestination(dst io.Writer)
    
    Events() *ee.EventEmitter
}
Service

The Service interface represents a generic service within the Mesh interface. It defines methods for initializing the service, retrieving its name, and a method that returns whether the service is ready to be used.

type Service interface {
    Init(Mesh)
    Name() string
    Ready() bool
}
HasDependencies

The HasDependencies interface extends the Service interface and adds methods for managing dependencies. It allows services to declare their dependencies, and to declare when they are resolved. The concrete implementation of the Mesh interface will use this HasDependencies interface to resolves any dependencies before the Init() method of a given service is invoked. This is an optional interface, your services do not need to implement this.

type HasDependencies interface {
	Service
    DependenciesResolved() bool
    ResolveDependencies(services []servicemesh.Service)
}
HasLogger

The HasLogger interface represents services that depend on a logger for logging purposes. It defines methods for setting the logger instance and retrieving the logger. This is an optional interface, your services do not need to implement this.

type HasLogger interface {
    SetLogger(logger *slog.Logger)
    Logger() *slog.Logger
}

This interface can be implemented by your services to define their behavior and interactions with the service mesh. They enable flexible dependency resolution, logging integration, and more.

Make sure to import the log/slog library for using the slog.Logger type in your service implementations.

HasGracefulShutdown

The HasGracefulShutdown interface is an extension of the Service interface that provides a standardized way to handle graceful shutdown for services. It defines the OnShutdown() method, which allows services to perform custom actions before they are stopped during the shutdown process.

To use the HasGracefulShutdown interface, implement it in your service struct and provide the implementation for the OnShutdown() method.

type MyService struct {
	// Service fields
}

func (s *MyService) Init(m Mesh) {
	// Initialization logic for your service
}

func (s *MyService) Name() string {
    return "MyService"
}

func (s *MyService) Ready() bool {
    return true
}

func (s *MyService) OnShutdown() {
	// Custom shutdown logic for your service
}

Events

The mesh comes integrated with an event emitter, which is modeled after the ee3 implementation in javascript. This is a singleton instance and is referred to as the "event bus." There is a single method of the Mesh (Events()) that will yield this singleton event emitter instance, and all services will have an opportunity to use or store a reference to this event emitter during their Init methods.

The Mesh has a list of events that it will emit during normal operation:

const (
	EventServiceAdded       = "service added"
	EventServiceRemoved     = "service removed"
	EventServiceInitialized = "service initialized"
	EventServiceEventsBound = "service events bound"
	EventServiceLoggerBound = "service logger bound"

	EventRuntimeRunLoopInitiated  = "runtime begin"
	EventRuntimeShutdownInitiated = "runtime shutdown"

	EventDependencyResolutionStarted = "runtime dependency resolution start"
	EventDependencyResolutionEnded   = "runtime dependency resolution end"
)

As opposed to forcing direct usage of the event emitter instance, there are a handful of integration interfaces which can be optionally implemented by a service. These can be found in interfaces.go. The concrete implementation of the mesh found in this module dog-foods the event-bus and event handler integration interfaces, and is actually a Service too. Much of the logging functionality is implemented through event handlers for events it is emitting.

NOTE

Notice that the Add, Remove, and Shutdown methods of the Mesh each yield a sync.Waitgroup instance. This allows the caller an opportunity to wait for event-handler callbacks to finish executing:

mesh := servicemesh.New()
mesh.Add(&foo.Service{}).Wait() // blocking call

This functionality can be especially handy in a scenario where you have services that are responsible for managing instances of subordinate services.

Contributing

Contributions are welcome! If you have any ideas, suggestions, or bug reports, please open an issue or submit a pull request. Let's make this package even better together.

License

This project is licensed under the MIT License.

Documentation

Index

Constants

View Source
const (
	EventServiceAdded       = "service added"
	EventServiceRemoved     = "service removed"
	EventServiceInitialized = "service initialized"
	EventServiceEventsBound = "service events bound"
	EventServiceLoggerBound = "service logger bound"

	EventServiceMeshRunLoopInitiated  = "run-loop initiated"
	EventServiceMeshShutdownInitiated = "shutdown initiated"

	EventDependencyResolutionStarted = "dependency resolution start"
	EventDependencyResolutionEnded   = "dependency resolution end"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type EventHandlerDependencyResolutionEnded

type EventHandlerDependencyResolutionEnded interface {
	OnDependencyResolutionEnded(service Service)
}

EventHandlerDependencyResolutionEnded is an optional interface. If implemented, it will automatically bind to the "Dependency Resolution Ended" service mesh event, enabling the implementor to respond when dependency resolution ends. When the event is emitted, the declared method will be called and passed the arguments from the emitter.

type EventHandlerDependencyResolutionStarted

type EventHandlerDependencyResolutionStarted interface {
	OnDependencyResolutionStarted(service Service)
}

EventHandlerDependencyResolutionStarted is an optional interface. If implemented, it will automatically bind to the "Dependency Resolution Started" service mesh event, enabling the implementor to respond when dependency resolution starts. When the event is emitted, the declared method will be called and passed the arguments from the emitter.

type EventHandlerServiceAdded

type EventHandlerServiceAdded interface {
	OnServiceAdded(service Service)
}

EventHandlerServiceAdded is an optional interface. If implemented, it will automatically bind to the "Service Added" service mesh event, allowing the handler to respond when a new service is added.

type EventHandlerServiceEventsBound

type EventHandlerServiceEventsBound interface {
	OnServiceEventsBound(service Service)
}

EventHandlerServiceEventsBound is an optional interface. If implemented, it will automatically bind to the "Service Events Bound" service mesh event, enabling the implementor to respond when events are bound to a service. When the event is emitted, the declared method will be called and passed the arguments from the emitter.

type EventHandlerServiceInitialized

type EventHandlerServiceInitialized interface {
	OnServiceInitialized(service Service)
}

EventHandlerServiceInitialized is an optional interface. If implemented, it will automatically bind to the "Service Initialized" service mesh event, enabling the implementor to respond when a service is initialized. When the event is emitted, the declared method will be called and passed the arguments from the emitter.

type EventHandlerServiceLoggerBound

type EventHandlerServiceLoggerBound interface {
	OnServiceLoggerBound(service Service)
}

EventHandlerServiceLoggerBound is an optional interface. If implemented, it will automatically bind to the "Service Logger Bound" service mesh event, enabling the implementor to respond when a logger is bound to a service. When the event is emitted, the declared method will be called and passed the arguments from the emitter.

type EventHandlerServiceMeshRunLoopInitiated

type EventHandlerServiceMeshRunLoopInitiated interface {
	OnServiceMeshRunLoopInitiated()
}

EventHandlerServiceMeshRunLoopInitiated is an optional interface. If implemented, it will automatically bind to the "mesh Run Loop Initiated" service mesh event, enabling the implementor to respond when the service mesh run loop is initiated. When the event is emitted, the declared method will be called and passed the arguments from the emitter.

type EventHandlerServiceMeshShutdownInitiated

type EventHandlerServiceMeshShutdownInitiated interface {
	OnServiceMeshShutdownInitiated()
}

EventHandlerServiceMeshShutdownInitiated is an optional interface. If implemented, it will automatically bind to the "mesh Shutdown Initiated" service mesh event, enabling the implementor to respond when the service mesh is preparing to shut down. When the event is emitted, the declared method will be called and passed the arguments from the emitter.

type EventHandlerServiceRemoved

type EventHandlerServiceRemoved interface {
	OnServiceRemoved(service Service)
}

EventHandlerServiceRemoved is an optional interface. If implemented, it will automatically bind to the "Service Removed" service mesh event, enabling the implementor to respond when a service is removed.

type HasDependencies

type HasDependencies interface {
	Service

	// DependenciesResolved returns true if all dependencies are resolved. This
	// is up to the service.
	DependenciesResolved() bool

	// ResolveDependencies attempts to resolve the dependencies of the
	// service using the provided Mesh.
	ResolveDependencies(services []Service)
}

HasDependencies represents a service that can resolve its dependencies.

The HasDependencies interface extends the Service interface and adds methods for managing dependencies. It allows services to declare whether their dependencies are resolved, as well as a method that attempts to resolve those dependencies with the given service mesh.

The mesh will use this interface automatically when a service is added. You do not need to implement this interface, it is optional. You would want to do this when you have services that depend upon each other to operate

type HasGracefulShutdown

type HasGracefulShutdown interface {
	Service

	// OnShutdown is called during the graceful shutdown process to perform
	// custom actions before the service is stopped.
	OnShutdown()
}

HasGracefulShutdown is an interface for services that require graceful shutdown handling.

The HasGracefulShutdown interface extends the Service interface and adds a method for performing custom actions during graceful shutdown.

type HasLogger

type HasLogger interface {
	Service
	// UseLogger sets the logger instance for the component.
	SetLogger(l *slog.Logger)
	// Logger yields the logger instance for the component.
	Logger() *slog.Logger
}

HasLogger is an interface for services that require a logger instance.

The HasLogger interface represents components that depend on a logger for logging purposes. It defines a method to set the logger instance.

type Mesh

type Mesh interface {
	// Add a single service to the Mesh.
	Add(Service) *sync.WaitGroup

	// Remove a specific service from the Mesh.
	Remove(Service) *sync.WaitGroup

	// Services returns a pointer to a slice of Services currently managed by
	// the service Mesh **which are ready to be used**.
	Services() []Service

	Events() *ee.EventEmitter

	Run()
	Shutdown() *sync.WaitGroup
	// contains filtered or unexported methods
}

Mesh is the abstract idea of the service mesh, an interface.

The Mesh interface defines the operations that can be performed with services, such as adding, removing, and retrieving services. It acts as a container for services and uses other interfaces like HasDependencies to work with them and do things automatically on their behalf.

func New

func New(args ...string) Mesh

New creates a new instance of a service mesh. Optionally, strings can be supplied as arguments which are concatenated to form the name of the service mesh during logging.

type Service

type Service interface {
	// Init initializes the service and establishes a connection to the
	// service Mesh.
	Init(mesh Mesh)

	// Name returns the name of the service.
	Name() string

	// Ready returns whether the service is ready to be used
	Ready() bool
}

Service represents a generic service within a service mesh.

The Service interface defines the contract that all services in the service mesh must adhere to. It provides methods for initializing the service and retrieving its name.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL