README
¶
nomix: Tagging and Metadata for Go
In the world of software engineering, we often deal with a vast array of assets like files, user profiles, products in an online store. These assets form the backbone of application data structures, but as systems grow more complex, simply storing the core data isn't enough. That's where metadata comes into play. Metadata provides a way to add extra layers of information and organization to these assets, making systems more adaptable.
nomix – from nomos (law/order) or “nomen” (name).
Installation
To use nomix in your Go project, install it with:
go get github.com/ctx42/nomix
Introduction
The nomix module distinguishes two main types of information:
- metadata is a set of named values of any type (
map[string]any). - tags is a set of named values each with a specific type (
map[string]Tag).
The metadata values are more flexible where tags are more restricted values
that implement the Tag interface.
// Tag is an interface representing a tag.
//
// Tags are named and typed values that can be used to annotate objects.
type Tag interface {
// TagName returns tag name.
TagName() string
// TagKind returns the [TagKind] holding the information about the type of
// the tag value. Use it interprets the value returned by the [Tag.TagValue]
// method.
TagKind() TagKind
// TagValue returns tag value.
// You may use the value returned by the [Tag.TagKind] method
// to cast it to the proper type.
TagValue() any
}
Kinds
The TagKind identifies the underlying Go type and can be used to decide what
database type to map it to. The nomix module defines and implements the
following set of base kinds:
nomix.KindString- in Go represented bystringtype.nomix.KindInt64- in Go represented byint64type.nomix.KindFloat64- in Go represented byfloat64type.nomix.KindTime- in Go represented bytime.Timetype.nomix.KindJSON- in Go represented byjson.RawMessagetype.
The base kinds represent known Go types and at the same time reflect the most
common database types. The idea is to provide the small set of base types and
derive the other types from them.
The derived kinds implemented in nomix are:
nomix.KindBool- in Go represented bybooltype.nomix.KindInt- in Go represented byinttype.
Slices
All the kinds (except nomix.KindJSON) have the SliceKind equivalents.
nomix.KindByteSlice- in Go represented by[]bytetype.nomix.KindStringSlice- in Go represented by[]stringtype.nomix.KindInt64Slice- in Go represented by[]int64type.nomix.KindFloat64Slice- in Go represented by[]float64type.nomix.KindTimeSlice- in Go represented by[]time.Timetype.nomix.KindBoolSlice- in Go represented by[]booltype.nomix.KindIntSlice- in Go represented by[]inttype.
Instances
Tge nomix module provides a Tag interface implementations for all the kinds:
nomix.Stringandnomix.StringSlicenomix.Int64andnomix.Int64Slicenomix.Float64andnomix.Float64Slicenomix.Timeandnomix.TimeSlicenomix.Boolandnomix.BoolSlicenomix.Intandnomix.IntSlicenomix.ByteSlice
Tag Set
The Tag interface provides the basis for operating on sets of differently
typed and named values in a generic way.
Anything can become a set of tags by implementing the Tagger interface.
// Tagger is an interface for managing a collection of distinctly named [Tag]
// instances (set). The implementations must not allow for nil values to be
// stored in the set.
type Tagger interface {
// TagGet retrieves from the set a [Tag] by its name. If the name doesn't
// exist in the set, it returns nil.
TagGet(name string) Tag
// TagSet adds instances of [Tag] to the set. If the tag name already
// exists in the set, it will be overwritten. The nil instances are ignored.
TagSet(tag ...Tag)
// TagDelete removes from the set the [Tag] by name. If the name does not
// exist, the method has no effect.
TagDelete(name string)
}
But nomix module provides a TagSet type that implements the Tagger and
adds some additional functionality around getting typed values from the set.
set := NewTagSet()
set.TagSet(NewInt("A", 42), NewBool("B", true), NewString("C", "foo"))
fmt.Printf("There are %d tags in the set:\n", set.TagCount())
fmt.Printf("- A: %v\n", set.TagGet("A").TagValue())
fmt.Printf("- B: %v\n", set.TagGet("B").TagValue())
fmt.Printf("- C: %v\n", set.TagGet("C").TagValue())
fmt.Printf("\nGetting typed tags:\n")
// Tag exists but is of a different type.
tagA, err := set.TagGetInt64("A")
fmt.Printf(" A: %v; err: %v\n", tagA, err)
tagC, err := set.TagGetString("C")
fmt.Printf(" C: %v; err: %v\n", tagC, err)
// Output:
// There are 3 tags in the set:
// - A: 42
// - B: true
// - C: foo
//
// Getting typed tags:
// A: <nil>; err: A: invalid element type
// C: foo; err: <nil>
Meta Set
Similarly to tags the nomix module provides equivalent interfaces and
structures for metadata.
// Metadata is an interface for managing a collection of distinctly named
// metadata values. The implementations must not allow for nil values to be
// stored in the set.
type Metadata interface {
// MetaGet retrieves from the set a metadata value by its name. If the name
// does not exist in the set, it returns nil.
MetaGet(name string) any
// MetaSet adds a named value to the set. If the value with the given name
// already exists in the set, it will be overwritten. Setting a nil value
// must be implemented as a no-op.
MetaSet(name string, value any)
// MetaDelete removes from the set the metadata value by name. If the name
// does not exist, the method has no effect.
MetaDelete(name string)
}
Similar example as for tags with MetaSet.
set := NewMetaSet()
set.MetaSet("A", 42)
set.MetaSet("B", true)
set.MetaSet("C", "foo")
fmt.Printf("There are %d entries in the set:\n", set.MetaCount())
fmt.Printf("- A: %v\n", set.MetaGet("A"))
fmt.Printf("- B: %v\n", set.MetaGet("B"))
fmt.Printf("- C: %v\n", set.MetaGet("C"))
fmt.Printf("\nGetting metadata values:\n")
// Tag exists but is of a different type.
metaA, err := set.MetaGetBool("A")
fmt.Printf(" A: %v; err: %v\n", metaA, err)
metaC, err := set.MetaGetString("C")
fmt.Printf(" C: %v; err: %v\n", metaC, err)
// Output:
// There are 3 entries in the set:
// - A: 42
// - B: true
// - C: foo
//
// Getting metadata values:
// A: false; err: A: invalid element type
// C: foo; err: <nil>