kdone

package module
v0.2.9 Latest Latest
Warning

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

Go to latest
Published: May 29, 2021 License: MIT Imports: 1 Imported by: 3

README

KDone

Go Reference codecov Report Card

Installation

go get github.com/go-kata/kdone

Status

This is a beta version. API is not stabilized for now.

Versioning

Till the first major release minor version (v0.x.0) must be treated as a major version and patch version (v0.0.x) must be treated as a minor version.

For example, changing of version from v0.1.0 to v0.1.1 indicates compatible changes, but when version changes v0.1.1 to v0.2.0 this means that the last version breaks the API.

How to use

This library is designed to simplify code of constructors and provide guaranteed finalization even on panic.

Here is an example code of application lifecycle (read comments, they are informative):

type Application struct {
	
	// Let's say that we don't use the database directly.
	database *Database

	Logger   *Logger
	Consumer *Consumer
}

func NewApplication() (*Application, error) {
	
	// We may use DI here instead, but let's consider this constructor
	// as topmost and encapsulating the whole application initialization.
	
	logger, err := NewLogger()
	if err != nil {
		return nil, err
	}
	// We can't use defer here - if we do this all initialized resources 
	// will be finalized at the end of constructor and resulting application 
	// instance will be broken.
	
	database, err := NewDatabase(logger)
	if err != nil {
		
		// It won't happen in case of panic.
		_ = logger.Close()
		
		return nil, err
	}
	
	consumer, err := NewConsumer(logger, database)
	if err != nil {
		
		// It won't happen in case of panic.
		_ = database.Close()
		_ = logger.Close()
		
		return nil, err
	}
	
	return &Application{database, logger, consumer}, nil
}

func (app *Application) Close() error {
	var errs []error
	
	if err := app.Consumer.Close(); err != nil {
		errs = append(errs, err)
	}
	
	// It won't happen in case of panic.
	if err := app.database.Close(); err != nil {
		errs = append(errs, err)
	}
	
	// It won't happen in case of panic.
	if err := app.Logger.Close(); err != nil {
		errs = append(errs, err)
	}
	
	if len(errs) > 0 {
		return errs[0]
	}
	return nil
}

type VerboseApplication struct {
	*Application
}

func NewVerboseApplication() (*VerboseApplication, error) {
	app, err := NewApplication()
	if err != nil {
		return nil, err
	}
	app.Logger.Print("application is up")
	return &VerboseApplication{app}, nil
}

func (app *VerboseApplication) Close() error {
	app.Logger.Print("application is down")
	if err := app.Close(); err != nil {
		return err
	}
}

// ...

app, err := NewApplication()
if err != nil {
	HandleError(err)
}
defer CloseWithErrorHandling(app)

Of course, there are some assumptions, e.g. logger will be closed even when database that depends on it was not successfully closed. But it is enough for a simple example.

This code may be rewritten using the library as follows (read comments, they are informative):

type Application struct {
	Logger   *Logger
	Consumer *Consumer
}

func NewApplication() (_ *Application, _ kdone.Destructor, err error) {
	
	// Just to transform panic to error. May be omitted.
	defer kerror.Catch(&err)
	
	// Reaper will call all destructors at the end
	// if wasn't released from this responsibility.
	reaper := kdone.NewReaper()
	defer reaper.MustFinalize()
	
	logger, dtor, err := NewLogger()
	if err != nil {
		return nil, nil, err
	}
	// Destructor will be called anyway - even in case of panic
	// on other initialization steps or in other destructors.
	//
	// We don't loose errors returned from destructors - all of them
	// will be aggregated into one using kerror.Collector.
	//
	// Panic in destructor will be transformed to error.
	reaper.MustAssume(dtor)
	
	// Let's say that Database implements the io.Closer interface
	// instead of returning a dedicated destructor.
	database, err := NewDatabase(logger)
	if err != nil {
		return nil, nil, err
	}
	reaper.MustAssume(kdone.DestructorFunc(database.Close))
	
	consumer, dtor, err := NewConsumer(logger, database)
	if err != nil {
		return nil, nil, err
	}
	reaper.MustAssume(dtor)
	
	// Now an external code is responsible for calling destructors -
	// reaper is released from this responsibility.
	return &Application{logger, consumer}, reaper.MustRelease(), nil
}

func NewVerboseApplication() (*Application, kdone.Destructor, error) {
	app, dtor, err := NewApplication()
	if err != nil {
		return nil, err
	}
	app.Logger.Print("application is up")
	return app, kdone.DestructorFunc(func() error {
		app.Logger.Print("application is down")
		return dtor.Destroy()
	}), err
}

// ...

app, dtor, err := NewApplication()
if err != nil {
	HandleError(err)
}
// Here CloseWithErrorHandling expects io.Closer.
defer CloseWithErrorHandling(kdone.CloserFunc(dtor.Destroy))

// We can't forget to use the dtor variable - it's the compile-time error.

This implementation is shorter (even despite lengthy comments), contains fewer entities and gives more guarantees of successful finalization.

Destructor may be easily converted to or from io.Closer thanks to kdone.CloserFunc and kdone.DestructorFunc helpers. If you need an idiomatic resource with the Close method as its part you may write something like this:

type ClosableResource struct {
	*Resource
	io.Closer
}

res, dtor, _ := NewResource()
resWithClose := ClosableResource{res, kdone.CloserFunc(dtor.Destroy)}

References

KError is the library that provides tools for handling errors.

Documentation

Overview

Package kdone provides tools for destroying objects.

Index

Constants

This section is empty.

Variables

View Source
var Noop = DestructorFunc(func() error {
	return nil
})

Noop specifies the destructor that does nothing.

Functions

This section is empty.

Types

type CloserFunc added in v0.1.1

type CloserFunc func() error

CloserFunc represents a functional implementation of the io.Closer interface.

func (CloserFunc) Close added in v0.1.1

func (f CloserFunc) Close() error

Close implements the io.Closer interface.

type Destructor

type Destructor interface {
	// DestroyStack destroys an associated object.
	Destroy() error
	// MustDestroy is a variant of the Destroy that panics on error.
	MustDestroy()
}

Destructor represents an object destructor.

The usual identifier for variables of this type is dtor.

type DestructorFunc

type DestructorFunc func() error

DestructorFunc represents a functional implementation of the Destructor interface.

func (DestructorFunc) Destroy

func (f DestructorFunc) Destroy() error

Destroy implements the Destructor interface.

func (DestructorFunc) MustDestroy

func (f DestructorFunc) MustDestroy()

MustDestroy implements the Destructor interface.

type Reaper

type Reaper struct {
	// contains filtered or unexported fields
}

Reaper represents a mechanism of deferred call of destructors.

func NewReaper

func NewReaper() *Reaper

NewReaper returns a new reaper.

func (*Reaper) Assume

func (r *Reaper) Assume(dtor Destructor) error

Assume passes the responsibility for calling the given destructor to this reaper.

func (*Reaper) Finalize

func (r *Reaper) Finalize() error

Finalize calls destructors in the backward order if this reaper was not released from this responsibility yet.

func (*Reaper) Finalized added in v0.2.6

func (r *Reaper) Finalized() bool

Finalized returns boolean specifies whether did this reapers call destructors.

func (*Reaper) MustAssume

func (r *Reaper) MustAssume(dtor Destructor)

MustAssume is a variant of the Assume that panics on error.

func (*Reaper) MustFinalize

func (r *Reaper) MustFinalize()

MustFinalize is a variant of the Finalize that panics on error.

func (*Reaper) MustRelease

func (r *Reaper) MustRelease() Destructor

MustRelease is a variant of the Release that panics on error.

func (*Reaper) Release

func (r *Reaper) Release() (Destructor, error)

Release releases this reaper from the responsibility for calling destructors and pass it to caller by return a composite destructor that calls destructors in the backward order.

func (*Reaper) Released added in v0.2.6

func (r *Reaper) Released() bool

Released returns boolean specifies whether was this reaper released from the responsibility for calling destructors.

Jump to

Keyboard shortcuts

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