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 ¶
- func KeyDeserialize(serialized string) ([]string, error)
- func KeySerialize(key ...string) string
- type EmptyKey
- type Instruction
- type Key
- func (receiver Key) CanonicalForm() string
- func (receiver Key) Else(key ...string) Key
- func (receiver Key) ElseUnwrap(key ...string) []string
- func (receiver Key) Format(f fmt.State, c rune)
- func (receiver Key) GoString() string
- func (receiver Key) Map(fn func(...string) []string) Key
- func (receiver Key) String() (string, error)
- func (receiver Key) Then(fn func(...string) Key) Key
- func (receiver Key) Value() (driver.Value, error)
- type KeyFound
- type KeyValues
- func (receiver *KeyValues) Fetch(key ...string) String
- func (receiver *KeyValues) For(fn func(Key, string))
- func (receiver *KeyValues) Len() int
- func (receiver *KeyValues) Load(key Key) String
- func (receiver *KeyValues) ShallowStore(key string, value string) error
- func (receiver *KeyValues) Store(key Key, value string) error
- type String
- func (receiver String) Else(datum string) String
- func (receiver String) ElseUnwrap(datum string) string
- func (receiver String) Format(f fmt.State, c rune)
- func (receiver String) GoString() string
- func (receiver String) Map(fn func(string) string) String
- func (receiver *String) Scan(src interface{}) error
- func (receiver String) String() (string, error)
- func (receiver String) Then(fn func(string) String) String
- func (receiver *String) UnmarshalText(text []byte) error
- func (receiver String) Unwrap() (string, bool)
- func (receiver String) Value() (driver.Value, error)
- type UnsupportedSource
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func KeyDeserialize ¶
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 ¶
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 ¶
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 ¶
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 ¶
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) ElseUnwrap ¶
func (Key) GoString ¶
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()
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) ShallowStore ¶
ShallowStore being called as:
err := keyvalues.ShallowStore(key, value)
... is equivalent to calling Store as:
err := keyvalues.Store(mdl.SomeKey(key), value)
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 ¶
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) ElseUnwrap ¶
func (String) GoString ¶
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) UnmarshalText ¶
Scan makes mdl.String fit the encoding.TextUnmarshaler 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
}
}