Code Monkey home page Code Monkey logo

kdone's Introduction

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.

kdone's People

Contributors

alexeymaximov avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.