crud

package
v0.136.1 Latest Latest
Warning

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

Go to latest
Published: Apr 9, 2023 License: Apache-2.0 Imports: 3 Imported by: 0

README

CRUD port

Using formalized CRUD (Create, Read, Update, Delete) interfaces in your software design has numerous benefits that can lead to more robust, maintainable, and scalable applications. It also reduce the learning curve for new team members.

Easy Testing

When you create an adapter for a specific role interface, you can effortlessly incorporate the CRUD contract testing suite. This suite verifies that your implementation behaves consistently with other solutions, meeting the expectations of the domain code.

package adapter_test

import (
	"context"
	"testing"
	
	"github.com/adamluzsi/testcase/assert"

	"github.com/adamluzsi/frameless/ports/crud/crudcontracts"
)

func TestMyAdapter(t *testing.T) {
	crudcontracts.SuiteFor[
		mydomain.Foo, mydomain.FooID,
		myadapter.FooRepository,
	](func(tb testing.TB) crudcontracts.SuiteSubject[
		mydomain.Foo, mydomain.FooID,
		myadapter.FooRepository,
	] {
		c, err := myadapter.NewConnection(...)
		assert.NoError(tb, err)
		tb.Cleanup(func() { c.Close() })
		r := myadapter.NewFooRepository(c)
		return crudcontracts.SuiteSubject[
			mydomain.Foo, mydomain.FooID,
			myadapter.FooRepository,
		]{
			Resource:              r,
			CommitManager:         c,
			MakeContext:           context.Background,
			MakeEntity:            func() mydomain.Foo { return /* make random Entity here without ID */ },
			CreateSupportIDReuse:  true,
			CreateSupportRecreate: true,
		}
	}).Test(t)
}

Notable benefits of using the crud port

Consistency

CRUD interfaces provide a standardized way to interact with your application's data, ensuring that developers can easily understand and work with the codebase. This promotes a consistent design pattern across the entire application, making it easier to maintain and extend.

Abstraction

CRUD interfaces abstract the underlying implementation details of data storage and manipulation, allowing developers to focus on the business logic instead of the specifics of the data source. This means that you can easily swap out the data source (e.g., from a local file to a remote database) without having to change the application code.

Flexibility

By using CRUD interfaces, you can easily represent a RESTful API resource from an external system or expose your own entities on your HTTP API.

Caching

Wrapping a repository that uses CRUD interfaces with caching is straightforward and can be done without leaking implementation details into your domain layer. This can improve performance by reducing the number of calls to the underlying data source, which might be slow or have limited resources.

Example use-cases where utilising CRUD port can benefit your system design

TL;DR: using formalized CRUD interfaces in your software design offers consistency, abstraction, flexibility, and the ability to easily incorporate caching. By employing these interfaces, you can more easily interact with external RESTful API resources, expose your entities on your HTTP API, and improve your application's performance. The provided Go code demonstrates a set of CRUD interfaces that can be implemented to achieve these benefits.

Repository Pattern

The Repository pattern is a design pattern used in software development to abstract the way data is stored, fetched, and manipulated. It acts as a middle layer between the data source (such as a database or an API) and the business logic of the application. By decoupling the data access logic from the rest of the application, the Repository pattern promotes separation of concerns, maintainability, and testability.

In the Repository pattern, a repository is responsible for performing CRUD (Create, Read, Update, Delete) operations on a specific entity or a group of related entities. It provides a consistent interface to interact with the underlying data source, allowing developers to focus on the business logic rather than the specifics of data access. This also makes it easier to switch to a different data source or introduce new data sources without having to modify the application's core logic.

Working with External System's RESTful API as Resource

Imagine you want to integrate an external system, like a third-party API, into your application. By using CRUD interfaces, you can define a repository that communicates with the external API and maps the external resources to your internal data models. The CRUD operations can then be used to interact with the external system in a standardized way.

Exposing Entities on HTTP API

Suppose you want to expose your application's entities on an HTTP API. Using CRUD interfaces, you can create a generic RESTful handler that uses your repository as a data source. This handler can then be used to handle requests and perform the necessary CRUD operations on your entities, making it easy to implement the HTTP API without having to write custom code for each operation.

Documentation

Index

Constants

View Source
const (
	ErrAlreadyExists errorutil.Error = "err-already-exists"
	ErrNotFound      errorutil.Error = "err-not-found"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type AllDeleter added in v0.80.0

type AllDeleter interface {
	// DeleteAll will erase all entity from the resource that has <V> type
	DeleteAll(context.Context) error
}

type AllFinder added in v0.80.0

type AllFinder[Entity any] interface {
	// FindAll will return all entity that has <V> type
	FindAll(context.Context) iterators.Iterator[Entity]
}

type ByIDDeleter added in v0.80.0

type ByIDDeleter[ID any] interface {
	// DeleteByID will remove a <V> type entity from the repository by a given ID
	DeleteByID(ctx context.Context, id ID) error
}

type ByIDFinder added in v0.80.0

type ByIDFinder[Entity, ID any] interface {
	// FindByID is a function that tries to find an Entity using its ID.
	// It will inform you if it successfully located the entity or if there was an unexpected issue during the process.
	// Instead of using an error to represent a "not found" situation,
	// a return boolean value is used to provide this information explicitly.
	//
	//
	// Why the return signature includes a found bool value?
	//
	// This approach serves two key purposes.
	// First, it ensures that the go-vet tool checks if the 'found' boolean variable is reviewed before using the entity.
	// Second, it enhances readability and demonstrates the function's cyclomatic complexity.
	//   total: 2^(n+1+1)
	//     -> found/bool 2^(n+1)  | An entity might be found or not.
	//     -> error 2^(n+1)       | An error might occur or not.
	//
	// Additionally, this method prevents returning an initialized pointer type with no value,
	// which could lead to a runtime error if a valid but nil pointer is given to an interface variable type.
	//   (MyInterface)((*Entity)(nil)) != nil
	//
	// Similar approaches can be found in the standard library,
	// such as SQL null value types and environment lookup in the os package.
	FindByID(ctx context.Context, id ID) (ent Entity, found bool, err error)
}

type Creator

type Creator[Entity any] interface {
	// Create is a function that takes a pointer to an entity and stores it in an external resource.
	// And external resource could be a backing service like PostgreSQL.
	// The use of a pointer type allows the function to update the entity's ID value,
	// which is significant in both the external resource and the domain layer.
	// The ID is essential because entities in the backing service are referenced using their IDs,
	// which is why the ID value is included as part of the entity structure fieldset.
	//
	// The pointer is also employed for other fields managed by the external resource, such as UpdatedAt, CreatedAt,
	// and any other fields present in the domain entity but controlled by the external resource.
	Create(ctx context.Context, ptr *Entity) error
}

type Deleter

type Deleter[ID any] interface {
	ByIDDeleter[ID]
	AllDeleter
}

Deleter request to destroy a business entity in the Resource that implement it's test.

type Finder

type Finder[Entity, ID any] interface {
	ByIDFinder[Entity, ID]
	AllFinder[Entity]
}

type Purger

type Purger interface {
	// Purge will completely wipe all state from the given resource.
	// It is meant to be used in testing during clean-ahead arrangements.
	Purge(context.Context) error
}

Purger supplies functionality to purge a resource completely. On high level this looks similar to what Deleter do, but in case of an event logged resource, this will purge all the events. After a purge, it is not expected to have anything in the repository. It is heavily discouraged to use Purge for domain interactions.

type Updater

type Updater[Entity any] interface {
	// Update will take a pointer to an entity and update the stored entity data by the values in received entity.
	// The Entity must have a valid ID field, which referencing an existing entity in the external resource.
	Update(ctx context.Context, ptr *Entity) error
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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