dataobject

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Mar 15, 2025 License: MIT Imports: 9 Imported by: 22

README

Data Object Open in Gitpod

A data object is a special purpose structure that is designed to hold data and track the changes to allow efficient serialization to a data store.

It follows the following principles:

  1. Has a default, non-argument constructor
  2. Has a unique identifier (ID) allowing to safely find the object among any other
  3. All the fields are private, thus non-modifiable from outside
  4. The fields are only accessed via public setter (mutator) and getter (accessor) methods
  5. All changes are tracked, and returned on request as a map of type map[string]string
  6. All data can be returned on request as a map of type map[string]string

Any object can be considered a data object as long as it adheres to the above principles, regardless of its specific implementation.

The implementation in this repo is just one way to implement the above principles. Other variations are possible to suit specific needs.

The concept is a bit similar to a POJO and a Java Bean.

Constructor Functions

The package provides the following constructor functions:

  • New() - Creates a new data object with a generated ID
  • NewFromData(data map[string]string) - Creates a data object from existing data
  • NewFromJSON(jsonString string) - Creates a data object from a JSON string
  • NewFromGob(gobData []byte) - Creates a data object from a gob-encoded byte array

The following constructor functions are deprecated and will be removed in a future version:

  • NewDataObject() - Use New() instead
  • NewDataObjectFromExistingData(data map[string]string) - Use NewFromData(data) instead
  • NewDataObjectFromJSON(jsonString string) - Use NewFromJSON(jsonString) instead

Usage

This is a full fledged example of a User data object taken from real life.

The example shows how to create new data object, set and get fields. Add helper methods to work with the fields.

Optional ORM-like relationship methods are also included. Use these with caution as these may create dependencies (i.e. with your services, repos, etc) that you may not want and need.

package models

import (
	"github.com/gouniverse/dataobject"
	"github.com/gouniverse/uid"
	"github.com/golang-module/carbon/v2"
)

// User is a data object
type User struct {
	dataobject.DataObject
}

// =============================  CONSTRUCTORS =============================

// NewUser instantiates a new user
func NewUser() *User {
	o := &User{}
	o.SetID(uid.HumanUid())
	o.SetStatus("active")
	o.SetCreatedAt(carbon.Now(carbon.UTC).ToDateTimeString(carbon.UTC))
	o.SetUpdatedAt(carbon.Now(carbon.UTC).ToDateTimeString(carbon.UTC))
	return o
}

// NewUserFromExistingData helper method to hydrate an existing user data object
func NewUserFromExistingData(data map[string]string) *User {
	o := &User{}
	o.Hydrate(data)
	return o
}

// ======================== RELATIONS/ORM (OPTIONAL) ===========================

func (o *User) Messages() []Messages {
	return NewMessageService.GetUserMessages(o.GetID())
}


func (o *User) Create() error {
	return NewUserService.Create(o)
}

func (o *User) Update() error {
	if !o.IsDirty() {
		return nil // object has not been changed
	}

	return NewUserService.Update(o)
}

// ================================ METHODS ====================================

func (o *User) IsActive() bool {
	return o.Status() == "active"
}

// ============================ GETTERS AND SETTERS ============================

func (o *User) CreatedAt() string {
	return o.Get("created_at")
}

func (o *User) CreatedAtCarbon() carbon.Carbon {
	return carbon.Parse(o.CreatedAt(), carbon.UTC)
}

func (o *User) SetCreatedAt(createdAt string) *User {
	o.Set("created_at", createdAt)
	return o
}

func (o *User) FirstName() string {
	return o.Get("first_name")
}

func (o *User) SetFirstName(firstName string) *User {
	o.Set("first_name", firstName)
	return o
}

func (o *User) LastName() string {
	return o.Get("last_name")
}

func (o *User) SetLastName(lastName string) *User {
	o.Set("last_name", lastName)
	return o
}

func (o *User) MiddleNames() string {
	return o.Get("middle_names")
}

func (o *User) SetMiddleNames(middleNames string) *User {
	o.Set("middle_names", middleNames)
	return o
}

func (o *User) Status() string {
	return o.Get("status")
}

func (o *User) SetStatus(status string) *User {
	o.Set("status", status)
	return o
}

func (o *User) UpdatedAt() string {
	return o.Get("updated_at")
}

func (o *User) UpdatedAtCarbon() carbon.Carbon {
	return carbon.Parse(o.UpdatedAt(), carbon.UTC)
}

func (o *User) SetUpdatedAt(updatedAt string) *User {
	o.Set("updated_at", updatedAt)
	return o
}

For an object using the above specifications:

// Create new user with autogenerated default ID
user := NewUser()

// Create new user with already existing data (i.e. from database)
user := NewUserFromData(data)

// returns the ID of the object
id := user.ID()

// example setter method
user.SetFirstName("John")

// example getter method
firstName := user.FirstName()

// find if the object has been modified
isDirty := user.IsDirty()

// returns the changed data
dataChanged := user.DataChanged()

// returns all the data
data := user.Data()

Basic Usage with DataObject

Here's how to use the DataObject directly:

// Create a new data object with a generated ID
do := dataobject.New()

// Create a data object from existing data
data := map[string]string{
    "id": "unique-id-123",
    "name": "Test Object",
    "value": "42",
}
do := dataobject.NewFromData(data)

// Create a data object from JSON
jsonStr := `{"id":"json-id-456","name":"JSON Object","active":"true"}`
do, err := dataobject.NewFromJSON(jsonStr)
if err != nil {
    // Handle error
}

// Create a data object from gob-encoded data
gobData, _ := existingDataObject.ToGob()
do, err := dataobject.NewFromGob(gobData)
if err != nil {
    // Handle error
}

// Get and set values
do.Set("name", "New Name")
name := do.Get("name")

// Check if the object has been modified
if do.IsDirty() {
    // Handle changes
    changedData := do.DataChanged()
}

// Get all data
allData := do.Data()

Comparison with Other Patterns

DataObject has been compared with various similar patterns from different programming languages and paradigms:

These comparisons provide insights into the design decisions, trade-offs, and use cases for each approach.

Saving data

Saving the data is left to the end user, as it is specific for each data store.

Some stores (i.e. relational databases) allow to only change specific fields, which is why the DataChanged() getter method should be used to identify the changed fields, then only save the changes efficiently.

func SaveUserChanges(user User) bool {
    if !user.IsDirty() {
        return true
    }

    changedData := user.DataChanged()

    return save(changedData)
}

Note! Some stores (i.e. document stores, file stores) save all the fields each time, which is where the Data() getter method should be used

func SaveFullUser(user User) bool {
    if !user.IsDirty() {
        return true
    }

    allData := user.Data()

    return save(allData)
}

Serialize to JSON

jsonString, err := user.ToJSON()


if err != nil {
    log.Fatal("Error serializing")
}

log.Println(jsonString)

Deserialize from JSON

user, err := NewFromJSON(jsonString)

if err != nil {
    log.Fatal("Error deserializing")
}

user.Get("first_name")

Serialize to Gob

gobData, err := user.ToGob()

if err != nil {
    log.Fatal("Error serializing to gob")
}

// Use gobData for efficient Go-to-Go data transfer or storage

Deserialize from Gob

user, err := NewFromGob(gobData)

if err != nil {
    log.Fatal("Error deserializing from gob")
}

user.Get("first_name")

Installation

To install the package, run the following command:

go get github.com/gouniverse/dataobject

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type DataObject

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

func New added in v1.0.0

func New() *DataObject

New creates a new data object with a unique ID

Business logic: - instantiates a new data object - generates an ID, using uid.HumanUid()

Note! The object is marked as dirty, as ID is set

Returns: - a new data object

func NewDataObject deprecated

func NewDataObject() *DataObject

Deprecated: NewDataObject is deprecated, use New() instead. Creates a new data object with a unique ID

func NewDataObjectFromExistingData deprecated

func NewDataObjectFromExistingData(data map[string]string) *DataObject

Deprecated: NewDataObjectFromExistingData is deprecated, use NewFromData() instead. Creates a new data object and hydrates it with the passed data

func NewDataObjectFromJSON deprecated added in v0.1.0

func NewDataObjectFromJSON(jsonString string) (do *DataObject, err error)

Deprecated: NewDataObjectFromJSON is deprecated, use NewFromJSON() instead. Creates a new data object and hydrates it with the passed JSON string

func NewFromData added in v1.0.0

func NewFromData(data map[string]string) *DataObject

NewFromData creates a new data object and hydrates it with the passed data

Note: the object is marked as not dirty, as it is existing data

Business logic: - instantiates a new data object - hydrates it with the passed data

Returns: - a new data object

func NewFromGob added in v1.0.0

func NewFromGob(gobData []byte) (*DataObject, error)

NewFromGob creates a new data object and hydrates it with the passed gob-encoded byte array.

The gob data is expected to be an encoded map[string]string

Note: the object is marked as not dirty, as it is existing data

Business logic: - instantiates a new data object - decodes the gob data - hydrates it with the decoded data

Returns: - a new data object - an error if any

func NewFromJSON added in v1.0.0

func NewFromJSON(jsonString string) (*DataObject, error)

NewFromJSON creates a new data object and hydrates it with the passed JSON string.

The JSON string is expected to be a valid DataObject JSON object

Note: the object is marked as not dirty, as it is existing data

Business logic: - instantiates a new data object - hydrates it with the passed JSON string

Returns: - a new data object - an error if any

func (*DataObject) Data

func (do *DataObject) Data() map[string]string

Data returns all the data of the object

func (*DataObject) DataChanged

func (do *DataObject) DataChanged() map[string]string

DataChanged returns only the modified data

func (*DataObject) Get

func (do *DataObject) Get(key string) string

Get helper getter method

func (*DataObject) Hydrate

func (do *DataObject) Hydrate(data map[string]string)

Hydrate sets the data for the object without marking it as dirty

func (*DataObject) ID

func (do *DataObject) ID() string

ID returns the ID of the object

func (*DataObject) Init

func (do *DataObject) Init()

Init initializes the data object if it is not already initialized

func (*DataObject) IsDirty

func (do *DataObject) IsDirty() bool

IsDirty returns if data has been modified

func (*DataObject) MarkAsNotDirty added in v0.2.0

func (do *DataObject) MarkAsNotDirty()

MarkAsNotDirty marks the object as not dirty

func (*DataObject) Set

func (do *DataObject) Set(key string, value string)

Set helper setter method

func (*DataObject) SetData

func (do *DataObject) SetData(data map[string]string)

SetData sets the data for the object and marks it as dirty see Hydrate for assignment without marking as dirty

func (*DataObject) SetID

func (do *DataObject) SetID(id string)

SetID sets the ID of the object

func (*DataObject) ToGob added in v1.0.0

func (do *DataObject) ToGob() ([]byte, error)

ToGob converts the DataObject to a gob-encoded byte array

Returns: - the gob-encoded byte array representation of the DataObject - an error if any

func (*DataObject) ToJSON added in v0.1.0

func (do *DataObject) ToJSON() (string, error)

ToJSON converts the DataObject to a JSON string

Returns: - the JSON string representation of the DataObject - an error if any

type DataObjectInterface

type DataObjectInterface interface {

	// ID returns the ID of the object
	ID() string

	// SetID sets the ID of the object
	SetID(id string)

	// GetData returns the data for the object
	Data() map[string]string

	// GetChangedData returns the data that has been changed since the last hydration
	DataChanged() map[string]string

	// Hydrates the data object with data
	Hydrate(map[string]string)

	// ToJSON converts the DataObject to a JSON string
	ToJSON() (string, error)

	// ToGob converts the DataObject to a gob-encoded byte array
	ToGob() ([]byte, error)
}

DataObjectInterface is an interface for a data object

Jump to

Keyboard shortcuts

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