mdl

package module
v0.0.0-...-cced04d Latest Latest
Warning

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

Go to latest
Published: Jul 20, 2019 License: MIT Imports: 15 Imported by: 0

README

go-mdl

Package mdl provides tools for doing Event Modeling in the Go programming language.

One might use package mdl as part of a system that does Event Sourcing, and CQRS.

Documention

Online documentation, which includes examples, can be found at: http://godoc.org/github.com/reiver/go-mdl

GoDoc

Documentation

Overview

Package mdl provides tools for doing Event Modeling, which one might use as part of a system that does Event Sourcing, and CQRS.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func KeyDeserialize

func KeyDeserialize(serialized string) ([]string, error)

KeyDeserialize returns the deserliaized version of a ‘serialized’.

In packge ‘mdl’, a ‘key’ isn't just a ‘string’, and is instead (conceptually) a ‘[]string’.

Because many things cannot work with a ‘[]string’, we have a serliaized form of the ‘key’.

Example

Here is an example of using ‘mdl.KeyDeserialze()’.

key := mdl.KeyDeserialize("database/password") // == []string{"database", "password"}

This is a more low-level function. Usually you will probably want to use the type ‘mdl.Key’.

func KeySerialize

func KeySerialize(key ...string) string

KeySerialize returns the serliaized version of a ‘key’.

In packge ‘mdl’, a ‘key’ isn't just a ‘string’, and is instead (conceptually) a ‘[]string’.

Because many things cannot work with a ‘[]string’, we have a serliaized form of the ‘key’.

Example

Here is an example of using ‘mdl.KeySerialze()’.

serialized := mdl.KeySerialize("database", "password") // == "database/password"

This is a more low-level function. Usually you will probably want to use the type ‘mdl.Key’.

Types

type EmptyKey

type EmptyKey interface {
	error
	EmptyKey()
}

EmptyKey is the error returned from mdl.KeyValues.Store() when .Store() is called with a key that has the value of ‘mdl.NoKey()’.

For example:

var keyvalues mdl.KeyValues

// ...

err := keyvalues.Store(value, key...)

if nil != err {
	switch err.(type) {
	case mdl.EmptyKey:
		//@TODO
	default:
		//@TODO
	}
}

type Instruction

type Instruction struct {
	IdempotentID String
	Verb         String
	Data         KeyValues
}

Instruction represents a command, order, or direction.

Some example instructions might be:

• “empty that shopping cart”,

• “add this book to that shopping cart”,

• “add this item to that TODO list”, and

• “add this e-mail address to my profile”.

In CQRS terminology, a ‘mdl.Instruction’ would be the equivalent of a CQRS ‘command’ (i.e., the “C” in “CQRS”).

HTTP Request

Nowadays it is common to expose APIs over HTTP (or HTTPS).

To help with this, ‘mdl.Instruction’ includes support for receiving ‘instructions’ over HTTP (or HTTPS).

Since Go has the built-in "net/http" package for dealing with HTTP (and HTTPS), ‘mdl.Instruction’ can infer the instruction from an ‘http.Request’.

To do this, one would use mdl.Instruction's .Scan() method; doing something along the lines of:

func (receiver *MyHandler) ServeHTTP(responseWriter ResponseWriter, request *Request) {

	// ...

	var instruction mdl.Instruction

	// ...

	err := instruction.Scan(request)
	switch err.(type) {
	case mdl.BadRequest:
		http.Error(responseWriter, "Bad Request", http.StatusBadRequest)
		return
	default:
		http.Error(responseWriter, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	// ...

}

This example Go source represents the ‘server’ side of the ‘instruction’. On the ‘client’ side of the ‘instruction’ we might have JavaScript code like the following:

httpRequest = new XMLHttpRequest();

// ...

httpRequest.open("POST", "https://api.example.com/v1/users");

// This ‘X-Idempotent-ID’ HTTP request header would be different for each ‘instruction’.
//
// Although if, for example, you don't get a response back for an ‘instruction’, and
// you wanted to re-try the ‘instruction’, then you would use the same value for
// ‘X-Idempotent-ID’.
httpRequest.setRequestHeader("X-Idempotent-ID", "z-2015-05-07T10:25:09Z_tleEiguQe67zJFYUa7pngSZT8HX7FMAcHb1Z4yOO2ANtltRPRwF5p9TWwf7m");

// Here we tell the server we are overriding the actual HTTP method used (i.e., "POST"
// in this example) to be instead be the non-common HTTP method “RECORD_EMAIL”.
httpRequest.setRequestHeader("X-HTTP-Method-Override", "RECORD_EMAIL");

httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
httpRequest.send('email_address=joeblow@example.com');

Or this:

httpRequest = new XMLHttpRequest();

// ...

httpRequest.open("RECORD_EMAIL", "https://api.example.com/v1/users");

// This ‘X-Idempotent-ID’ would be different for each ‘instruction’.
//
// Although if, for example, you don't get a response back for an ‘instruction’, and
// you wanted to re-try the ‘instruction’, then you would use the same value for
// ‘X-Idempotent-ID’.
httpRequest.setRequestHeader("X-Idempotent-ID", "z-2015-05-07T10:25:09Z_tleEiguQe67zJFYUa7pngSZT8HX7FMAcHb1Z4yOO2ANtltRPRwF5p9TWwf7m");

httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
httpRequest.send('email_address=joeblow@example.com');

Idempotent ID

IdempotentID is a unique identifier (ID) that helps make it so an instruction is executed at most one time.

It helps make it so an instruction is idempotent.

Idempotent ID From HTTP Request

To provide an IdempotentID in an ‘http.Request’, have the HTTP client set the “X-Idempotent-ID” HTTP header.

For example:

PATCH /v1/user/email HTTP/1.1
Host: api.example.com
X-Idempotent-ID: z-2015-05-07T10:25:09Z_tleEiguQe67zJFYUa7pngSZT8HX7FMAcHb1Z4yOO2ANtltRPRwF5p9TWwf7m

email_address=joeblow@email.com

If someone is using XMLHttpRequest, to make the HTTP request, then this can be set with code similar to:

httpRequest = new XMLHttpRequest();

// ...

httpRequest.httpRequest("X-Idempotent-ID", "z-2015-05-07T10:25:09Z_tleEiguQe67zJFYUa7pngSZT8HX7FMAcHb1Z4yOO2ANtltRPRwF5p9TWwf7m")

How To Construct An Idempotent ID

I recommend using a combination of the current time, and randomness be used to contruct Idempotent IDs.

And if you want to help reduce memory thrashing, then the part with the current time should come before the part with the randomness.

This will make it so lexical order of the Idempotent IDs is often also a time ordering (for cases when the time between 2 IdempotentIDs is not exactly the same).

Verb

You can probably think of ‘Verb’ as the name of the instruction.

Some examples Verbs are:

• “empty that shopping cart”,

• “add this book to that shopping cart”,

• “add this item to that TODO list”, and

• “add this e-mail address to my profile”.

Although in the code you may encode these as:

• “EMPTY_SHOPPING_CART”,

• “ADD_TO_SHOPPING_CART”,

• “APPEND”,

• “RECORD_EMAIL”.

Verb From HTTP Request

To provide a Verb in an http.Request, have the HTTP client set the HTTP request method, or if that is not possible, use X-HTTP-Method-Override

So, consider this example HTTP request:

POST /v1/user/email HTTP/1.1
Host: api.example.com
X-Idempotent-ID: z-2015-05-07T10:25:09Z_tleEiguQe67zJFYUa7pngSZT8HX7FMAcHb1Z4yOO2ANtltRPRwF5p9TWwf7m

email_address=joeblow@email.com

The Verb here is “POST”.

Now consider this example that includes an “X-HTTP-Method-Override” header:

POST /v1/user/email HTTP/1.1
Host: api.example.com
X-Idempotent-ID: z-2015-05-07T10:25:09Z_tleEiguQe67zJFYUa7pngSZT8HX7FMAcHb1Z4yOO2ANtltRPRwF5p9TWwf7m
X-HTTP-Method-Override: ADD_EMAIL_ADDRESS

email_address=joeblow@email.com

The Verb here is “ADD_EMAIL_ADDRESS”.

Now consider this example that uses a non-regular HTTP method:

ADD_EMAIL_ADDRESS /v1/user/email HTTP/1.1
Host: api.example.com
X-Idempotent-ID: z-2015-05-07T10:25:09Z_tleEiguQe67zJFYUa7pngSZT8HX7FMAcHb1Z4yOO2ANtltRPRwF5p9TWwf7m

email_address=joeblow@email.com

Again the Verb here is “ADD_EMAIL_ADDRESS”.

If someone is using XMLHttpRequest, to make the HTTP request, then this can be set with code similar to:

httpRequest = new XMLHttpRequest();

// ...

httpRequest.open("ADD_EMAIL_ADDRESS", url);

Or:

httpRequest = new XMLHttpRequest();

// ...

httpRequest.open("POST", url);
httpRequest.setRequestHeader("X-HTTP-Method-Override", "ADD_EMAIL_ADDRESS")

func (*Instruction) Scan

func (receiver *Instruction) Scan(src interface{}) error

Scan makes ‘mdl.Instruction’ fit the ‘database/sql.Scanner’ interface.

HTTP Request

It also provides an opinionated way of receiving an instruction from an HTTP request. I.e., it uses a specific convention for receiving an instruction from an HTTP request.

The instruction ‘Verb’ is given by the HTTP request method. The normal HTTP request method can be overridden by the “X-HTTP-Method-Override” header.

The instruction ‘Idempotent ID’ is given by the value of the “X-Idempotent-ID” header.

The instruction ‘data’ is given by the HTTP request body.

Example HTTP Request

Here is an example of .Scan() being used to receive the instruction from the HTTP request.

func (receiver *MyHandler) ServeHTTP(responseWriter ResponseWriter, request *Request) {

	// ...

	var cmd mdl.Instruction

	// ...

	err := cmd.Scan(request)

type Key

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

Key represents the ‘key’ in the key-value pairs.

Because the ‘key’ isn't just a ‘string’, and is instead (conceptually) a ‘[]string’, ‘mdl.Key’ exists to abstract that complexity, and make the API for this package a bit easier to use.

Example

Here is an example of setting the value of ‘mdl.Key’ using a non-serialized format:

var key mdl.Key

// ...

key = mdl.SomKey("database", "password")

Example

Here is an example of setting the value of ‘mdl.Key’ using a serialized format:

var key mdl.Key

// ...

err := key.Scan("database/password")

func NoKey

func NoKey() Key

NoKey returns a ‘mdl.Key’ which has no value (i.e., ‘nothing’).

Example

Here is an example of ‘mdl.NoKey()’ being used in an assignment.

var key mdl.Key

// ...

key = mdl.NoKey()

You can also use ‘mdl.NoKey()’ in comparisons in an if-statment, as in for example:

var key mdl.Key

// ...

if mdl.NoKey() == key {
	//@TODO
}

And you can use ‘mdl.NoKey()’ in comparisons in an switch-statment, as in for example:

var key mdl.Key

// ...

switch key {
case mdl.NoKey():
	//@TODO

case mdl.SomeKey("database", "username"):
	//@TODO
case mdl.SomeKey("database", "password"):
	//@TODO
case mdl.SomeKey("version"):
	//@TODO

default:
	//@TODO
}

func SomeKey

func SomeKey(key ...string) Key

SomeKey returns a ‘mdl.Key’ which has some value (i.e., ‘something’).

Example

Here is an example of ‘mdl.SomeKey()’ being used in an assignment.

var key mdl.Key

// ...

key = mdl.SomeKey("database", "password")

You can also use ‘mdl.SomeKey()’ in comparisons in an if-statment, as in for example:

var key mdl.Key

// ...

if mdl.SomeKey("database", "password") == key {
	//@TODO
}

And you can use ‘mdl.SomeKey()’ in comparisons in an switch-statment, as in for example:

var key mdl.Key

// ...

switch key {
case mdl.NoKey():
	//@TODO

case mdl.SomeKey("database", "username"):
	//@TODO
case mdl.SomeKey("database", "password"):
	//@TODO
case mdl.SomeKey("version"):
	//@TODO

default:
	//@TODO
}

func (Key) CanonicalForm

func (receiver Key) CanonicalForm() string

CanonicalForm returns the ‘mdl.Key’ in ‘canonical form’.

Example

For example, for this ‘mdl.Key’:

var key mdl.Key = mdl.SomeKey("database", "password")

... its canonical form’ form is:

database/password

func (Key) Else

func (receiver Key) Else(key ...string) Key

func (Key) ElseUnwrap

func (receiver Key) ElseUnwrap(key ...string) []string

func (Key) Format

func (receiver Key) Format(f fmt.State, c rune)

func (Key) GoString

func (receiver Key) GoString() string

GoString makes ‘mdl.Key’ fit the fmt.GoStringer interface.

It gets used with the %#v verb with the printing family of functions in the Go built-in "fmt" package.

I.e., it gets used with: fmt.Fprint(), fmt.Fprintf(), fmt.Fprintln(), fmt.Print(), fmt.Printf(), fmt.Println(), fmt.Sprint(), fmt.Sprintf(), fmt.Sprintln().

Example

Here is an example where .GoString() is being implicitly used. This implicit usage is the way most people are likely to use it.

var datum mdl.Key

// ...

fmt.Printf("datum = %#v\n", s) // <---- datum.GoString() is called by fmt.Printf()

func (Key) Map

func (receiver Key) Map(fn func(...string) []string) Key

func (Key) String

func (receiver Key) String() (string, error)

func (Key) Then

func (receiver Key) Then(fn func(...string) Key) Key

func (Key) Value

func (receiver Key) Value() (driver.Value, error)

Value makes mdl.Key fit the database/sql/driver.Valuer interface.

type KeyFound

type KeyFound interface {
	error
	KeyFound()

	// Key returns the key.
	Key() Key

	// Value returns the value that was trying to be stored at the key, when the error occurred.
	Value() string

	// FoundValue returns the value that was already stored at the key.
	FoundValue() string
}

KeyFound is the error returned from mdl.KeyValues.Store() when .Store() is called and there is already a value stored for that key.

For example:

var keyvalues mdl.KeyValues

// ...

err := keyvalues.Store(key, value)

if nil != err {
	switch err.(type) {
	case mdl.KeyFound:
		//@TODO
	default:
		//@TODO
	}
}

type KeyValues

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

KeyValues represents key-value pairs.

It stores key-value pairs.

‘mdl.KeyValues’ is the type of one of the fields for ‘mdl.Instruction’.

func (*KeyValues) Fetch

func (receiver *KeyValues) Fetch(key ...string) String

Fetch is similar to .Load().

func (*KeyValues) For

func (receiver *KeyValues) For(fn func(Key, string))

func (*KeyValues) Len

func (receiver *KeyValues) Len() int

func (*KeyValues) Load

func (receiver *KeyValues) Load(key Key) String

func (*KeyValues) ShallowStore

func (receiver *KeyValues) ShallowStore(key string, value string) error

ShallowStore being called as:

err := keyvalues.ShallowStore(key, value)

... is equivalent to calling Store as:

err := keyvalues.Store(mdl.SomeKey(key), value)

func (*KeyValues) Store

func (receiver *KeyValues) Store(key Key, value string) error

type String

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

String is a string option type.

It can have 2 kinds of value:

• no string (i.e., ‘nothing’),

• some string (i.e., ‘something’).

The value in this type is that it can differentiate between being assigned an empty string (""), and a variable that hasn't been loaded.

func NoString

func NoString() String

NoString returns a mdl.String which has no value (i.e., ‘nothing’).

Note that this is not the same thing as an empty string ("")! An empty string is mdl.SomeString("")

Example

Here is an example of mdl.NoString() being used in an assignment.

var datum mdl.String

// ...

datum = mdl.NoString()

You can also use mdl.NoString() in comparisons in an if-statment, as in for example:

var datum mdl.String

// ...

if mdl.NoString() == datum {
	//@TODO
}

And you can use mdl.NoString() in comparisons in an switch-statment, as in for example:

var datum mdl.String

// ...

switch datum {
case mdl.NoString():
	//@TODO

case mdl.SomeString("apple"):
	//@TODO
case mdl.SomeString("banana"):
	//@TODO
case mdl.SomeString("cherry"):
	//@TODO

default:
	//@TODO
}

func SomeString

func SomeString(datum string) String

SomeString returns a mdl.String which has some value (i.e., ‘something’).

Example

Here is an example of mdl.SomeString() being used in an assignment.

var datum mdl.String

// ...

datum = mdl.SomeString("Hello world!")

You can also use mdl.SomeString() in comparisons in an if-statment, as in for example:

var datum mdl.String

// ...

if mdl.SomeString("Hello world!") == datum {
	//@TODO
}

And you can use mdl.SomeString() in comparisons in an switch-statment, as in for example:

var datum mdl.String

// ...

switch datum {
case mdl.NoString():
	//@TODO

case mdl.SomeString("apple"):
	//@TODO
case mdl.SomeString("banana"):
	//@TODO
case mdl.SomeString("cherry"):
	//@TODO

default:
	//@TODO
}

func (String) Else

func (receiver String) Else(datum string) String

func (String) ElseUnwrap

func (receiver String) ElseUnwrap(datum string) string

func (String) Format

func (receiver String) Format(f fmt.State, c rune)

Format makes ‘mdl.String’ fit the ‘fmt.Formatter’ interface.

func (String) GoString

func (receiver String) GoString() string

GoString makes mdl.String fit the fmt.GoStringer interface.

It gets used with the %#v verb with the printing family of functions in the Go built-in "fmt" package.

I.e., it gets used with: fmt.Fprint(), fmt.Fprintf(), fmt.Fprintln(), fmt.Print(), fmt.Printf(), fmt.Println(), fmt.Sprint(), fmt.Sprintf(), fmt.Sprintln().

Example

var datum mdl.String

// ...

fmt.Printf("datum = %#v\n", s) // <---- datum.GoString() is called by fmt.Printf()

func (String) Map

func (receiver String) Map(fn func(string) string) String

func (*String) Scan

func (receiver *String) Scan(src interface{}) error

Scan makes mdl.String fit the database/sql.Scanner interface.

func (String) String

func (receiver String) String() (string, error)

func (String) Then

func (receiver String) Then(fn func(string) String) String

func (*String) UnmarshalText

func (receiver *String) UnmarshalText(text []byte) error

Scan makes mdl.String fit the encoding.TextUnmarshaler interface.

func (String) Unwrap

func (receiver String) Unwrap() (string, bool)

func (String) Value

func (receiver String) Value() (driver.Value, error)

Value makes mdl.String fit the database/sql/driver.Valuer interface.

type UnsupportedSource

type UnsupportedSource interface {
	error

	// This UnsupportedSource() method exists to allow type checking of the error.
	UnsupportedSource()

	// Debug returns something similar to Error(), but also include more information useful for debugging, such as the file name, and line number the error came from.
	Debug() string

	// Source returns value that caused this error.
	Source() interface{}
}

UnsupportedSource is the error returned from mdl.String.Scan(), and mdl.String.UnmarshalText() when the type of the source is not supported.

For example:

var source int64 // <---- Note that the type is int64, which mdl.String.Scan does not support (by itself). So it will return an error.

// ...

var datum mdl.String

// ...

err := datum.Scan(source)

if nil != err {
	switch err.(type) {
	case mdl.UnsupportedSource:
		//@TODO
	default:
		//@TODO
	}
}

Jump to

Keyboard shortcuts

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