soforo

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Feb 8, 2025 License: BSD-3-Clause Imports: 5 Imported by: 0

README

Soforo

Soforo is a Go package that helps with the implementation of a pluggable Driver and Repository model for your application. It is inspired by how the database/sql package works in Go.

In the database/sql package, there are generic interfaces that represent a connection to a database. You can then use these interfaces to perform operations on the database. The database/sql package also provides a way to register drivers for different databases. This way, you can use the same interface to interact with different databases.

In the same way, Soforo provides a way to define type of connections and a repository interface that can be implemented by different drivers. You can then use the repository interface to interact with the underlying connection.

Soforo does not manage the connections for you. It is up to the driver to implement the connection management. Soforo only provides a way to define the interface for the connection and the repository.

Soforo assumes that the connection specification is a URL. The scheme of the URL is used to determine the driver to use. The rest of the URL is passed to the driver to establish the connection.

Usage

To use Soforo, you need to define a connection type and a repository interface. You can then implement the connection type and the repository interface for different drivers.

Here is an example of how you can use Soforo to interact with a file system.


package storage

import (
	"github.com/amicolabs/soforo"
	"io/fs"
)

type Driver interface {
	soforo.Driver[Repository]
}

type Repository interface {
	soforo.Repository
	fs.FS
	fs.ReadFileFS

	WriteFile(name string, data []byte) error
}

var Drivers = soforo.NewDrivers[Driver, Repository]("storage")

// This interface should be implemented by the object that is passed to the 
// Open method of the driver of connection types that need a storage repository.
type Provider interface {
	Storage() Repository
}

In the example above, we define a Driver interface that extends the driver.Driver interface and a Repository interface that extends the soforo.Repository, fs.FS and fs.ReadFileFS interfaces. We then create a new driver registry using soforo.NewDrivers.

Next, we can implement the driver and the repository for the file system.


package file

import (
	"fmt"
    "io/fs"
    "net/url"
    "os"
    "path"

	"github.com/amicolabs/soforo/examples/storage"
)

type Driver struct{}

func (d Driver) Open(url *url.URL, _ interface{}) (storage.Repository, error) {
	p := url.Path
	if p == "" {
		p = url.Opaque
	}
	fsys, ok := os.DirFS(p).(FS)

	if !ok {
		return nil, fmt.Errorf("opening file system %s failed", url.Path)
	}

	return &Repository{path: p, fs: fsys}, nil
}

func (d Driver) Dependencies() []string {
	return []string{}
}

type FS interface {
	fs.FS
	fs.StatFS
	fs.ReadFileFS
	fs.ReadDirFS
}

type Repository struct {
	path string
	fs   FS
}

func (r *Repository) Open(name string) (fs.File, error) {
	return r.fs.Open(name)
}

func (r *Repository) Stat(name string) (fs.FileInfo, error) {
	return r.fs.Stat(name)
}

func (r *Repository) ReadFile(name string) ([]byte, error) {
	return r.fs.ReadFile(name)
}

func (r *Repository) ReadDir(name string) ([]fs.DirEntry, error) {
	return r.fs.ReadDir(name)
}

func (r *Repository) WriteFile(name string, data []byte) error {
	return os.WriteFile(path.Join(r.path, name), data, 0644)
}

func (r *Repository) Close() error {
	return nil
}

func init() {
	storage.Drivers.Register("file", Driver{})
}

This is an example of a file system driver. The driver implements the Driver interface and the repository implements the Repository interface.

This is how you can use the file system driver.


package main

import (
    "fmt"
    "net/url"

    "github.com/amicolabs/soforo/examples/storage"
    _ "github.com/amicolabs/soforo/examples/storage/file"
)

func main() {
    u, _ := url.Parse("file:///tmp")
    repo, _ := storage.Drivers.Open(u)
    data, _ := repo.ReadFile("test.txt")
    fmt.Println(string(data))
}

This exact interface could also be implemented for S3, etc. Using this model the underlying storage can be swapped out without changing the code that uses the repository interface.

Documentation

Overview

Package soforo provides a generic interface around drivers for various connection types. It can be used for databases, storage and other connection types.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Driver

type Driver[R Repository] interface {
	// Open returns a new Repository or an error. It is the caller's
	// responsibility to call Close on Repository when the Repository is no
	// longer needed. The provider argument is used to provide the driver with
	// additional context. Some drivers need other services to be available in
	// the provider struct. The driver must verify that the provided
	// instance is of the expected type and return an error if it is not.
	Open(url *url.URL, provider interface{}) (R, error)
}

Driver is the interface that must be implemented by a driver implementation. This interface is generic and should be extended by the consuming package to specify the type of Repository that the driver provides. In most cases, the Driver interface in the consuming packages won't need extra methods.

type Drivers

type Drivers[D Driver[R], R Repository] struct {
	// contains filtered or unexported fields
}

Drivers is a collection of drivers that can be used to open a repository. The drivers are stored in a map and can be registered using the Register method. The Open method can be used to open a repository using the URL. The URL scheme is used to determine which driver to use. The Drivers collection is safe for concurrent use, because it uses a mutex to synchronize access to the map.

func NewDrivers

func NewDrivers[D Driver[R], R Repository](name string) *Drivers[D, R]

NewDrivers returns a new Drivers collection.

func (*Drivers[D, R]) Driver

func (ds *Drivers[D, R]) Driver(u *url.URL) (D, error)

Driver returns the driver with the provided name. If the driver is not registered, an error is returned. It uses the URL scheme to determine which driver to use.

func (*Drivers[D, R]) DriverByName

func (ds *Drivers[D, R]) DriverByName(name string) (D, error)

DriverByName returns the driver with the provided name. If the driver is not registered, an error is returned.

func (*Drivers[D, R]) Drivers

func (ds *Drivers[D, R]) Drivers() []string

Drivers returns a sorted list of the names of the registered drivers.

func (*Drivers[D, R]) Open

func (ds *Drivers[D, R]) Open(u *url.URL, provider interface{}) (R, error)

Open opens a Repository from a driver using the URL. It is the caller's responsibility to call Close on Repository when the Repository is no longer needed. Open uses the URL scheme to determine which driver to use. If the driver is unknown, Open returns an error. The provider argument is used to provide the driver with additional context. The driver must verify that the provided instance is of the expected type and return an error if it is not.

func (*Drivers[D, R]) Register

func (ds *Drivers[D, R]) Register(name string, driver D)

Register makes a driver available by the provided name. If Register is called twice with the same name it panics.

type Repository

type Repository interface {
	Close() error
}

Repository the value that eventually ends up in the application. This interface should be extended by the consuming package to include the methods that the repository provides.

Jump to

Keyboard shortcuts

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