Composing Subjects
So far, subjects have been composed by hand.
To define their structure once, call the NewSubjectScheme function with a pattern, and use placeholders in braces for the variable parts:
var bookSubject = architecturekit.NewSubjectScheme("/books/{book}")
Each segment of a subject may only contain the characters that EventSourcingDB allows: the letters A-Z and a-z, the digits 0-9, underscores, and hyphens (see Subjects). This applies to the literal segments of the pattern as well as to the values that fill its placeholders. The names of the placeholders follow the same rule, as book and license-agreement do, so that a typo, such as a brace too many in {book}}, is not taken for a name.
The function returns a *SubjectScheme. To compose a subject, call the Build function with one value per placeholder, in the order in which they appear in the pattern. Use it in every command that acts on a book:
func (c AcquireBook) Subject() string {
return bookSubject.Build(c.BookID)
}
func (c BorrowBook) Subject() string {
return bookSubject.Build(c.BookID)
}
func (c ReturnBook) Subject() string {
return bookSubject.Build(c.BookID)
}
To take a subject apart, call the Match function. It returns the values by placeholder name, and false if the subject does not follow the pattern or has a value that Build would refuse:
values, ok := bookSubject.Match("/books/42")
// values["book"] == "42"
To take a single value out of a subject, call the Value function with the subject and the name of the placeholder. Like Match, it returns false if the subject does not follow the pattern:
bookID, ok := bookSubject.Value("/books/42", "book")
// bookID == "42"
Note that Value panics for a placeholder that the pattern does not have, such as one with a typo, whatever the subject is. The map that Match returns yields an empty string for such a name instead, so use Value for a single value, and Match for several values of the same subject.
To get the pattern and the names of the placeholders, call the Pattern and the Placeholders function respectively.
To read or observe the events of all books, for example in a projection, start from the subject that all of them lie under. Call the Root function to get it from the scheme, rather than writing it down a second time. It returns the literal segments before the first placeholder, here /books, or / if the pattern starts with a placeholder:
run := architecturekit.StartProjection(ctx, store, architecturekit.SubjectTree(bookSubject.Root()), projection)
Note that other subjects may lie under the same root, such as /books/42/reviews/7 under /books. Use Match in the projection to tell them apart.
Note that a malformed pattern panics, including one with a literal segment or a placeholder name that contains a character EventSourcingDB does not allow, as does calling Build with the wrong number of values, with an empty value, or with a value that contains such a character, for example a slash, a dot, or a space.
Values that come from outside, such as an ID in a request, may well be empty or contain such characters, and that is not a programming error. So always check them before building a subject: call the Check function with the same values as Build. It returns an error that says what is wrong, such as which characters a value may contain, instead of panicking. The error may reach the caller of an API, for example through the function that returns a command (see Handling Commands over HTTP), so it names the placeholder of the value, but neither the package nor the pattern, as in value for "book" must not be empty:
if err := bookSubject.Check(bookID); err != nil {
// ...
}