Skip to content

Commit

Permalink
Merge 368d65a into 99874ea
Browse files Browse the repository at this point in the history
  • Loading branch information
mustafaturan committed Feb 8, 2020
2 parents 99874ea + 368d65a commit 3b50690
Show file tree
Hide file tree
Showing 14 changed files with 266 additions and 60 deletions.
71 changes: 68 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,20 @@ generator.
Via go packages:
```go get github.com/mustafaturan/monoton```

## API

The method names and arities/args are stable now. No change should be expected
on the package for the version `1.x.x` except any bug fixes.

## Usage

### Using with Singleton

Create a new package if you want a singleton access to monoton id generator,
you can create a simple package like below, and then call Next() method:

```go
package main
package uniqid

// Import packages
import (
Expand All @@ -25,6 +35,8 @@ import (
"github.com/mustafaturan/monoton/sequencer"
)

var m monoton.Monoton

// On init configure the monoton
func init() {
// Fetch your node id from a config server or generate from MAC/IP address
Expand All @@ -37,12 +49,65 @@ func init() {
initialTime := uint64(0)

// Configure monoton with a sequencer and the node
monoton.Configure(sequencer.NewMillisecond(), node, initialTime)
m, err = monoton.New(sequencer.NewMillisecond(), node, initialTime)
if err != nil{
panic(err)
}
}

func Generate() string {
m.Next()
}
```

In any other package generate the ids like
```go
import (
"fmt"
"uniqid"
)

func main() {
for i := 0; i < 100; i++ {
fmt.Println(uniqid.Next())
}
}
```

### Using with Dependency Injection

```go
package main

// Import packages
import (
"fmt"
"github.com/mustafaturan/monoton"
"github.com/mustafaturan/monoton/sequencer"
)

func NewIDGenerator() *monoton.Monoton {
// Fetch your node id from a config server or generate from MAC/IP address
node := uint64(1)

// A unix time value which will be subtracted from the time sequence value.
// The initialTime value type corresponds to the sequencer type's time
// representation. If you are using Millisecond sequencer then it must be
// considered as Millisecond
initialTime := uint64(0)

// Configure monoton with a sequencer and the node
m, err = monoton.New(sequencer.NewMillisecond(), node, initialTime)
if err != nil{
panic(err)
}
}

func main() {
g := NewIDGenerator()

for i := 0; i < 100; i++ {
fmt.Println(monoton.Next())
fmt.Println(g.Next())
}
}
```
Expand Down
20 changes: 15 additions & 5 deletions example_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// Copyright 2020 Mustafa Turan. All rights reserved.
// Use of this source code is governed by a Apache License 2.0 license that can
// be found in the LICENSE file.

package monoton_test

import (
Expand All @@ -8,13 +12,16 @@ import (
"github.com/mustafaturan/monoton/sequencer"
)

func ExampleConfigure() {
func ExampleNew() {
s := sequencer.NewMillisecond() // has 4 bytes free space for a node
n := uint64(19) // Base62 => J
t := uint64(0) // initial time (start from unix time in ms)

monoton.Configure(s, n, t)
fmt.Println(monoton.Next()[12:])
m, err := monoton.New(s, n, t)
if err != nil {
panic(err)
}
fmt.Println(m.Next()[12:])
// Output:
// 000J
}
Expand All @@ -24,8 +31,11 @@ func ExampleNext() {
n := uint64(19) // Base62 => J
t := uint64(time.Now().Unix()) // initial time (start from unix time in s)

monoton.Configure(s, n, t)
fmt.Println(len(monoton.Next()))
m, err := monoton.New(s, n, t)
if err != nil {
panic(err)
}
fmt.Println(len(m.Next()))
// Output:
// 16
}
149 changes: 114 additions & 35 deletions monoton.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2019 Mustafa Turan. All rights reserved.
// Copyright 2020 Mustafa Turan. All rights reserved.
// Use of this source code is governed by a Apache License 2.0 license that can
// be found in the LICENSE file.

Expand Down Expand Up @@ -52,6 +52,54 @@
// The sequencers can be extended for any other time format, sequence format by
// implementing the monoton/sequncer.Sequencer interface.
//
// Example using Singleton
//
// package uniqid
//
// // Import packages
// import (
// "fmt"
// "github.com/mustafaturan/monoton"
// "github.com/mustafaturan/monoton/sequencer"
// )
//
// var m monoton.Monoton
//
// // On init configure the monoton
// func init() {
// // Fetch your node id from a config server or generate from MAC/IP address
// node := uint64(1)
//
// // A unix time value which will be subtracted from the time sequence value.
// // The initialTime value type corresponds to the sequencer type's time
// // representation. If you are using Millisecond sequencer then it must be
// // considered as Millisecond
// initialTime := uint64(0)
//
// // Configure monoton with a sequencer and the node
// m, err = monoton.New(sequencer.NewMillisecond(), node, initialTime)
// if err != nil{
// panic(err)
// }
// }
//
// func Generate() string {
// m.Next()
// }
//
// In any other package unique ids can be generated like
//
// import (
// "fmt"
// "uniqid"
// )
//
// func main() {
// for i := 0; i < 100; i++ {
// fmt.Println(uniqid.Generate())
// }
// }
//
package monoton

import (
Expand All @@ -63,33 +111,57 @@ import (
)

const (
totalByteSize = 16
maxNodeErrorMsg = "node can't be greater than %d (given %d)"
maxByteSizeErrorMsg = "sum of s:%d, t:%d bytes can't be >= total byte size"
totalByteSize = 16
errMaxNode = "node can't be greater than %d (given %d)"
errMaxByteSize = "max byte size sum of sequence(%d) and time sequence(%d) can't be >= total byte size(%d)"
)

type config struct {
// MaxNodeCapacityExceededError is an error type with node information
type MaxNodeCapacityExceededError struct {
Node uint64
MaxNode uint64
}

func (e *MaxNodeCapacityExceededError) Error() string {
return fmt.Sprintf(errMaxNode, e.MaxNode, e.Node)
}

// MaxByteSizeError is an error type with sequence & time byte sizes
type MaxByteSizeError struct {
ByteSizeSequence int64
ByteSizeSequenceTime int64
ByteSizeTotal int64

MaxSequence string
MaxSequenceTime string
}

func (e *MaxByteSizeError) Error() string {
return fmt.Sprintf(errMaxByteSize, e.ByteSizeSequence, e.ByteSizeSequenceTime, e.ByteSizeTotal)
}

// Monoton is a sequential id generator
type Monoton struct {
sequencer *sequencer.Sequencer
initialTime uint64
node string
timeSeqByteSize int64
seqByteSize int64
}

var c config
// New inits a new monoton ID generator with the given generator and node.
func New(s sequencer.Sequencer, node, initialTime uint64) (*Monoton, error) {
m := &Monoton{sequencer: &s, initialTime: initialTime}

// Configure configures the monoton with the given generator and node. If you
// need to reset the node, then you have to reconfigure. If you do not configure
// the node then the node will be set to zero value.
func Configure(s sequencer.Sequencer, node, initialTime uint64) error {
c = config{sequencer: &s, initialTime: initialTime}
if err := m.configureByteSizes(); err != nil {
return nil, err
}

if err := configureByteSizes(); err != nil {
return err
if err := m.configureNode(node); err != nil {
return nil, err
}
err := configureNode(node)

return err
return m, nil
}

// Next generates next incremental unique identifier as Base62
Expand All @@ -101,47 +173,54 @@ func Configure(s sequencer.Sequencer, node, initialTime uint64) error {
// Nanosecond: 16 B => 11 B (nanoseconds) + 2 B (counter) + 3 B (node)
//
// For byte size decisions please refer to docs/adrs/byte-sizes.md
func Next() string {
t, seq := (*c.sequencer).Next()
func (m *Monoton) Next() string {
t, seq := (*m.sequencer).Next()

return encoder.ToBase62WithPaddingZeros(t-c.initialTime, c.timeSeqByteSize) +
encoder.ToBase62WithPaddingZeros(seq, c.seqByteSize) +
c.node
return encoder.ToBase62WithPaddingZeros(t-m.initialTime, m.timeSeqByteSize) +
encoder.ToBase62WithPaddingZeros(seq, m.seqByteSize) +
m.node
}

func configureByteSizes() error {
sequencer := *c.sequencer
func (m *Monoton) configureByteSizes() error {
sequencer := *m.sequencer
maxSeqTime := encoder.ToBase62(uint64(sequencer.MaxTime()))
c.timeSeqByteSize = int64(len(maxSeqTime))
m.timeSeqByteSize = int64(len(maxSeqTime))

maxSeq := encoder.ToBase62(uint64(sequencer.Max()))
c.seqByteSize = int64(len(maxSeq))
m.seqByteSize = int64(len(maxSeq))

// At least one byte slot is necessary for the node
if c.seqByteSize+c.timeSeqByteSize >= totalByteSize {
return fmt.Errorf(maxByteSizeErrorMsg, c.seqByteSize, c.timeSeqByteSize)
if m.seqByteSize+m.timeSeqByteSize >= totalByteSize {
return &MaxByteSizeError{
ByteSizeSequence: m.seqByteSize,
ByteSizeSequenceTime: m.timeSeqByteSize,
ByteSizeTotal: totalByteSize,
MaxSequence: maxSeq,
MaxSequenceTime: maxSeqTime,
}
}

return nil
}

func configureNode(node uint64) error {
nodeByteSize := totalByteSize - (c.timeSeqByteSize + c.seqByteSize)

if err := validateNode(node, nodeByteSize); err != nil {
func (m *Monoton) configureNode(node uint64) error {
if err := m.validateNode(node); err != nil {
return err
}

c.node = encoder.ToBase62WithPaddingZeros(node, nodeByteSize)
m.node = encoder.ToBase62WithPaddingZeros(node, m.nodeByteSize())
return nil
}

func validateNode(node uint64, nodeByteSize int64) error {
maxNode := uint64(math.Pow(62, float64(nodeByteSize))) - 1

func (m *Monoton) validateNode(node uint64) error {
maxNode := uint64(math.Pow(62, float64(m.nodeByteSize()))) - 1
if node > maxNode {
return fmt.Errorf(maxNodeErrorMsg, maxNode, node)
return &MaxNodeCapacityExceededError{Node: node, MaxNode: maxNode}
}

return nil
}

func (m *Monoton) nodeByteSize() int64 {
return totalByteSize - (m.timeSeqByteSize + m.seqByteSize)
}

0 comments on commit 3b50690

Please sign in to comment.