domain

package
v0.0.0-...-f88c608 Latest Latest
Warning

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

Go to latest
Published: Feb 27, 2025 License: AGPL-3.0 Imports: 17 Imported by: 44

Documentation

Overview

Package domain implements domain services. Each domain service is responsible for providing APIs pertaining to a logical domain. Domains may cross entity boundaries; the partitioning is done based on packaging cohesive functional behaviour, not individual entity concerns.

Package layout and conventions

Each domain service package has several key artefacts:

  • the service providing public APIs called by API server facades.
  • the state providing functionality to read and write persistent data.
  • params structs which are used as arguments/results for service API calls.
  • arg structs which are used as arguments/results for state calls.
  • type structs which are used a sqlair in/out parameters.

The layout of a domain package providing a service is as follows:

	domain/foo/
	 |- types.go [1]
	 |- params.go [2]
	 |- service/
	   |- service.go
	 |- state/
	   |- state.gp
	   |- types.go [3]
	 |- errors/
	   |- errors.go
	 |- bootstrap/
	   |- bootstrap.go
	 |- modelmigration/
	   |- import.go
	   |- export.go

 [1] contains DTOs used as arguments/results for state calls
 [2] optional - contains structs used as arguments/results for service API calls
 [3] contains package private structs which act as in/out params for sqlair.

At the time of writing, many domain entity related structs are defined in juju/core. Where the sole use of these structs is to fulfil operational requirements of the relevant domain package, and there's no use outside the package, they can be moved down from core.

To avoid name clashes and promote consistency of implementation, a naming convention is used when defining structs used as method args and results. Some key conventions are as follows:

  • structs used as service API call args are named xxxParams.
  • structs used as state call args are named xxxArgs.

eg

func(s *Service) DoWork(ctx context.Context, p WorkParams) error {
    args := foo.ProgressArgs{
        StartedAt: time.Now(),
    }
    return s.st.RecordStart(ctx, args)
}

Testing

For the state layer, test suites embed a base suite depending on whether they are operating on a model or controller database.

eg

type applicationStateSuite struct {
    domaintesting.ModelSuite
}

Tests that need to seed database rows or read rows to evaluate results may do so using a sql txn. Note that any test assertions must be done outside the transaction.

eg

var gotFoo string
err := s.TxnRunner().StdTxn(context.Background(), func(ctx context.Context, tx *sql.Tx) error {
    err := tx.QueryRowContext(ctx, `SELECT foo FROM table`).Scan(&gotFoo)
    return err
})
c.Assert(err, jc.ErrorIsNil)
c.Assert(gotFoo, gc.Equals, "bar")

Tests are implemented by invoking business methods being tested on a state instance created from the base suite txn runner factory.

eg

st = NewFooState(s.TxnRunnerFactory(), loggertesting.WrapCheckLog(c))
err := st.Foo(context.Background())

Service layer tests are implemented using mocks which are set up via package_test.

eg

//go:generate go run go.uber.org/mock/mockgen -typed -package service -destination service_mock_test.go github.com/juju/juju/domain/foo/service FooState

Implementation notes

Implicit in the package and file structure defined above are the following rules which must be observed:

  • service and state packages must remain decoupled and not import from each other.

Package bootstrap provides methods called by github.com/juju/juju/agent/agentbootstrap.Initialize to seed the controller database with data required for a fully initialised controller.

Package modelmigration provides methods called by github.com/juju/juju/domain/modelmigration.RegisterExport and github.com/juju/juju/domain/modelmigration.RegisterImport in order to implement the export and import of model artefacts for migration.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CoerceError

func CoerceError(err error) error

CoerceError converts all sql, sqlite and dqlite errors into an error that is impossible to unwrwap, thus hiding the error from errors.As and errors.Is. This is done to prevent checking the error type at the wrong layer. All sql errors should be handled at the domain layer and not above. Thus we don't expose couple the API client/server to the database layer.

func LifeStringsWatcherMapperFunc

func LifeStringsWatcherMapperFunc(logger logger.Logger, lifeGetter LifeGetter) eventsource.Mapper

LifeStringsWatcherMapperFunc returns a namespace watcher mapper function which emits events when the life of an entity changes. The supplied lifeGetter func is used to retrieve a map of life values, keyed on IDs which must match those supplied by the source event stream. The source event stream may supply ids the caller is not interested in. These are filtered out after loading the current values from state.

func Run

func Run(ctx AtomicContext, fn func(context.Context, *sqlair.TX) error) error

Run executes the closure function using the provided AtomicContext as the transaction context. It is expected that the closure will perform state changes within the transaction scope. Any errors returned from the closure are coerced into a standard error to prevent sqlair errors from being returned to the Service layer. Deprecated: Use state directly.

Types

type AtomicContext

type AtomicContext interface {
	// Context returns the context that the transaction was created with.
	Context() context.Context
}

AtomicContext is a typed context that provides access to the database transaction for the duration of a transaction.

type AtomicStateBase

type AtomicStateBase interface {
	// RunAtomic executes the closure function within the scope of a
	// transaction. The closure is passed a AtomicContext that can be passed on
	// to state functions, so that they can perform work within that same
	// transaction. The closure will be retried according to the transaction
	// retry semantics, if the transaction fails due to transient errors. The
	// closure should only be used to perform state changes and must not be used
	// to execute queries outside of the state scope. This includes performing
	// goroutines or other async operations.
	// Deprecated: Use the StateBase struct directly.
	RunAtomic(ctx context.Context, fn func(AtomicContext) error) error
}

AtomicStateBase is an interface that provides a method for executing a closure within the scope of a transaction. Deprecated: Use the StateBase struct directly.

type LeaseService

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

LeaseService creates a base service that offers lease capabilities.

func NewLeaseService

func NewLeaseService(leaseChecker lease.ModelLeaseManagerGetter) *LeaseService

NewLeaseService creates a new LeaseService.

func (*LeaseService) LeadershipCheck

func (s *LeaseService) LeadershipCheck(appName, unitName string) leadership.Token

LeadershipCheck returns a token that can be used to check if the input unit is the leader of the input application.

func (*LeaseService) WithLeader

func (s *LeaseService) WithLeader(
	ctx context.Context, appName, unitName string, fn func(context.Context) error,
) error

WithLeader executes the closure function if the input unit is leader of the input application. As soon as that isn't the case, the context is cancelled and the function returns. The context must be passed to the closure function to ensure that the cancellation is propagated to the closure.

type LifeGetter

type LifeGetter func(ctx context.Context, db coredatabase.TxnRunner, ids []string) (map[string]life.Life, error)

LifeGetter is a function which looks up life values of the entities with the specified IDs.

type Preparer

type Preparer interface {
	Prepare(query string, typeSamples ...any) (*sqlair.Statement, error)
}

Preparer is an interface that prepares SQL statements for sqlair.

type StateBase

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

StateBase defines a base struct for requesting a database. This will cache the database for the lifetime of the struct.

func NewStateBase

func NewStateBase(getDB database.TxnRunnerFactory) *StateBase

NewStateBase returns a new StateBase.

func (*StateBase) DB

func (st *StateBase) DB() (TxnRunner, error)

DB returns the database for a given namespace.

func (*StateBase) Prepare

func (st *StateBase) Prepare(query string, typeSamples ...any) (*sqlair.Statement, error)

Prepare prepares a SQLair query. If the query has been prepared previously it is retrieved from the statement cache.

Note that because the type samples are not considered when retrieving a query from the cache, it is possible that two queries may have identical text, but use different types. Retrieving the wrong query would result in an error when the query was passed the wrong type at execution.

The likelihood of this happening is low since the statement cache is scoped to individual domains meaning that the two identically worded statements would have to be in the same state package. This issue should be relatively rare and caught by QA if present.

func (*StateBase) RunAtomic

func (st *StateBase) RunAtomic(ctx context.Context, fn func(AtomicContext) error) error

RunAtomic executes the closure function within the scope of a transaction. The closure is passed a AtomicContext that can be passed on to state functions, so that they can perform work within that same transaction. The closure will be retried according to the transaction retry semantics, if the transaction fails due to transient errors. The closure should only be used to perform state changes and must not be used to execute queries outside of the state scope. This includes performing goroutines or other async operations.

type TxnRunner

type TxnRunner interface {
	// Txn manages the application of a SQLair transaction within which the
	// input function is executed. See https://github.com/canonical/sqlair.
	// The input context can be used by the caller to cancel this process.
	Txn(context.Context, func(context.Context, *sqlair.TX) error) error
}

TxnRunner is an interface that provides a method for executing a closure within the scope of a transaction.

type WatchableDBFactory

type WatchableDBFactory = func() (changestream.WatchableDB, error)

WatchableDBFactory is a function that returns a WatchableDB or an error.

type WatcherFactory

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

WatcherFactory is a factory for creating watchers.

func NewWatcherFactory

func NewWatcherFactory(watchableDBFactory WatchableDBFactory, logger logger.Logger) *WatcherFactory

NewWatcherFactory returns a new WatcherFactory.

func (*WatcherFactory) NewNamespaceMapperWatcher

func (f *WatcherFactory) NewNamespaceMapperWatcher(
	namespace string, changeMask changestream.ChangeType,
	initialStateQuery eventsource.NamespaceQuery,
	mapper eventsource.Mapper,
) (watcher.StringsWatcher, error)

NewNamespaceMapperWatcher returns a new namespace watcher for events based on the input change mask and mapper.

func (*WatcherFactory) NewNamespaceNotifyMapperWatcher

func (f *WatcherFactory) NewNamespaceNotifyMapperWatcher(
	namespace string, changeMask changestream.ChangeType, mapper eventsource.Mapper,
) (watcher.NotifyWatcher, error)

NewNamespaceNotifyMapperWatcher returns a new namespace notify watcher for events based on the input change mask and mapper.

func (*WatcherFactory) NewNamespaceNotifyWatcher

func (f *WatcherFactory) NewNamespaceNotifyWatcher(
	namespace string, changeMask changestream.ChangeType,
) (watcher.NotifyWatcher, error)

NewNamespaceNotifyWatcher returns a new namespace notify watcher for events based on the input change mask.

func (*WatcherFactory) NewNamespaceWatcher

func (f *WatcherFactory) NewNamespaceWatcher(
	namespace string, changeMask changestream.ChangeType,
	initialStateQuery eventsource.NamespaceQuery,
) (watcher.StringsWatcher, error)

NewNamespaceWatcher returns a new namespace watcher for events based on the input change mask.

func (*WatcherFactory) NewUUIDsWatcher

func (f *WatcherFactory) NewUUIDsWatcher(
	tableName string, changeMask changestream.ChangeType,
) (watcher.StringsWatcher, error)

NewUUIDsWatcher returns a watcher that emits the UUIDs for changes to the input table name that match the input mask.

func (*WatcherFactory) NewValueMapperWatcher

func (f *WatcherFactory) NewValueMapperWatcher(
	namespace, changeValue string,
	changeMask changestream.ChangeType,
	mapper eventsource.Mapper,
) (watcher.NotifyWatcher, error)

NewValueMapperWatcher returns a watcher for a particular change value in a namespace, based on the input change mask and mapper.

func (*WatcherFactory) NewValueWatcher

func (f *WatcherFactory) NewValueWatcher(
	namespace, changeValue string, changeMask changestream.ChangeType,
) (watcher.NotifyWatcher, error)

NewValueWatcher returns a watcher for a particular change value in a namespace, based on the input change mask.

Directories

Path Synopsis
Package access provides the services for managing users and permissions in Juju.
Package access provides the services for managing users and permissions in Juju.
Package agentprovisioner defines the agent provisioner service, which the provisioner uses to retrieve container configuration for provisioning.
Package agentprovisioner defines the agent provisioner service, which the provisioner uses to retrieve container configuration for provisioning.
Package annotation provides a way to attach key-value pairs to object types.
Package annotation provides a way to attach key-value pairs to object types.
Package application provides the domain types for an application.
Package application provides the domain types for an application.
charm
Package charm provides the domain types for a charm.
Package charm provides the domain types for a charm.
blockdevice
Package cloud contains the service for managing clouds known to Juju.
Package cloud contains the service for managing clouds known to Juju.
Package cloudimagemetadata provides a service to store and retrieve cloud image metadata.
Package cloudimagemetadata provides a service to store and retrieve cloud image metadata.
Package controller holds the controller state and service.
Package controller holds the controller state and service.
controllerconfig
Package controllernode provides the service that keeps track of controller nodes.
Package controllernode provides the service that keeps track of controller nodes.
Package externalcontroller provides a service to keep track of external controllers.
Package externalcontroller provides a service to keep track of external controllers.
Package flag provides a service that keeps track of global boolean operational flags.
Package flag provides a service that keeps track of global boolean operational flags.
Package keys provides the domain needed for configuring public keys on a model for a user.
Package keys provides the domain needed for configuring public keys on a model for a user.
Package keyupdater provides the domain knowledge for retrieving the authorised keys for the different entities within Juju.
Package keyupdater provides the domain knowledge for retrieving the authorised keys for the different entities within Juju.
Package machine provides the services for managing machines in Juju.
Package machine provides the services for managing machines in Juju.
Package model contains the controller model service.
Package model contains the controller model service.
service
Package service contains the services required for interacting with the underlying models within a Juju controller.
Package service contains the services required for interacting with the underlying models within a Juju controller.
Package modelagent defines a domain to access information about the agents running in a model, such as the version.
Package modelagent defines a domain to access information about the agents running in a model, such as the version.
modelconfig
Package network provides the service for managing juju's networking aspects, namely spaces and subnets.
Package network provides the service for managing juju's networking aspects, namely spaces and subnets.
proxy
Package relation provides the domain types for application relations.
Package relation provides the domain types for application relations.
service
Package service provides the service methods for the relation domain.
Package service provides the service methods for the relation domain.
state
Package state provides the state methods used by the relation service for its domain.
Package state provides the state methods used by the relation service for its domain.
removal
Package resource provides the domain types for handling charm resources once associated with application.
Package resource provides the domain types for handling charm resources once associated with application.
Package schema contains the schema definitions for all the domains.
Package schema contains the schema definitions for all the domains.
state
Package upgrade state provides a type for representing the state of an upgrade.
Package upgrade state provides a type for representing the state of an upgrade.

Jump to

Keyboard shortcuts

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