github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/model/README.md (about)

     1  # Model
     2  
     3  Package model is a convenience wrapper around what the `Store` provides.
     4  It's main responsibility is to maintain indexes that would otherwise be maintaned by the users to enable different queries on the same data.
     5  
     6  ## Usage
     7  
     8  The following snippets will this piece of code prepends them.
     9  
    10  ```go
    11  import(
    12      model "github.com/tickoalcantara12/micro/v3/service/model"
    13      fs "github.com/tickoalcantara12/micro/v3/service/store/file"
    14  )
    15  
    16  type User struct {
    17  	ID      string `json:"id"`
    18   	Name string    `json:"name"`
    19  	Age     int    `json:"age"`
    20  	HasPet  bool   `json:"hasPet"`
    21  	Created int64  `json:"created"`
    22  	Tag     string `json:"tag"`
    23  	Updated int64  `json:"updated"`
    24  }
    25  ```
    26  
    27  ## Query by field equality
    28  
    29  For each field we want to query on we have to create an index. Index by `id` is provided by default to each `DB`, there is no need to specify it.
    30  
    31  ```go
    32  ageIndex := model.ByEquality("age")
    33  
    34  db := model.New(User{}, []model.Index{(ageIndex})
    35  
    36  err := db.Create(User{
    37      ID: "1",
    38      Name: "Alice",
    39      Age: 20,
    40  })
    41  if err != nil {
    42      // handle save error
    43  }
    44  err := db.Create(User{
    45      ID: "2",
    46      Name: "Jane",
    47      Age: 22
    48  })
    49  if err != nil {
    50      // handle save error
    51  }
    52  
    53  err = db.Read(model.Equals("age", 22), &users)
    54  if err != nil {
    55  	// handle list error
    56  }
    57  fmt.Println(users)
    58  
    59  // will print
    60  // [{"id":"2","name":"Jane","age":22}]
    61  ```
    62  
    63  ## Reading all records in an index
    64  
    65  Reading can be done without specifying a value:
    66  
    67  ```go
    68  db.Read(Equals("age", nil), &users)
    69  ```
    70  
    71  Readings will be unordered, ascending ordered or descending ordered depending on the ordering settings of the index.
    72  
    73  ## Ordering
    74  
    75  Indexes by default are ordered. If we want to turn this behaviour off:
    76  
    77  ```go
    78  ageIndex.Order.Type = OrderTypeUnordered
    79  
    80  ageQuery := model.Equals("age", 22)
    81  ageQuery.Order.Type = OrderTypeUnordered
    82  ```
    83  
    84  ### Filtering by one field, ordering by other
    85  
    86  ```go
    87  typeIndex := ByEquality("type")
    88  typeIndex.Order = Order{
    89  	Type:      OrderTypeDesc,
    90  	FieldName: "age",
    91  }
    92  
    93  // Results will be ordered by age
    94  db.Read(typeIndex.ToQuery("a-certain-type-value"))
    95  ```
    96  
    97  By default the ordering field is the same as the filtering field.
    98  
    99  ### Reverse order
   100  
   101  ```go
   102  ageQuery.Desc = true
   103  ```
   104  
   105  ### Queries must match indexes
   106  
   107  It is important to note that queries must match indexes. The following index-query pairs match (separated by an empty line)
   108  
   109  ```go
   110  // Ascending ordered index by age
   111  index := model.Equality("age")
   112  // Read ascending ordered by age
   113  query := model.Equals("age", nil)
   114  // Read ascending ordered by age where age = 20
   115  query2 := model.Equals("age", 20) 
   116  
   117  // Descending ordered index by age
   118  index := model.Equality("age")
   119  index.Order.Type = OrderTypeDesc
   120  // Read descending ordered by age
   121  query := model.Equals("age", nil)
   122  query.Order.Type = OrderTypeDesc
   123  // Read descending ordered by age where age = 20
   124  query2 := model.Equals("age", 20)
   125  query2.Order.Type = OrderTypeDesc
   126  
   127  // Unordered index by age
   128  index := model.Equality("age")
   129  index.Order.Type = OrderTypeUnordered
   130  // Read unordered by age
   131  query := model.Equals("age", nil)
   132  query.Order.Type = OrderTypeUnordered
   133  // Read unordered by age where age = 20
   134  query2 := model.Equals("age", 20)
   135  query2.Order.Type = OrderTypeUnordered
   136  ```
   137  
   138  Of course, maintaining this might be inconvenient, for this reason the `ToQuery` method was introduced, see below.
   139  
   140  #### Creating a query out of an Index
   141  
   142  ```go
   143  
   144  index := model.Equality("age")
   145  index.Order.Type = OrderTypeUnordered
   146  
   147  db.Read(index.ToQuery(25))
   148  ```
   149  
   150  ### Unordered listing without value
   151  
   152  It's easy to see how listing things by unordered indexes on different fields should result in the same output: a randomly ordered list, ie:
   153  
   154  ```go
   155  ageIndex := model.Equality("age")
   156  ageIndex.Order.Type = OrderTypeUnordered
   157  
   158  emailIndex := model.Equality("email")
   159  emailIndex.Order.Type = OrderTypeUnordered
   160  
   161  result1 := []User{}
   162  result2 := []User{}
   163  
   164  db.Read(model.Equals("age"), &result1)
   165  db.Read(model.Equals("email"), &result2)
   166  
   167  // Both result1 and result2 will be an unordered listing without
   168  // filtering on either the age or email fields.
   169  // Could be thought of as a noop query despite not having an explicit "no query" listing.
   170  ```
   171  
   172  ### Ordering by string fields
   173  
   174  Ordering comes for "free" when dealing with numeric or boolean fields, but it involves  in padding, inversing and order preserving base32 encoding of values to work for strings.
   175  
   176  This can sometimes result in large keys saved, as the inverse of a small 1 byte character in a string is a 4 byte rune. Optionally adding base32 encoding on top to prevent exotic runes appearing in keys, strings blow up in size even more. If saving space is a requirement and ordering is not, ordering for strings should be turned off.
   177  
   178  The matter is further complicated by the fact that the padding size must be specified ahead of time.
   179  
   180  ```go
   181  nameIndex := model.ByEquality("name")
   182  nameIndex.StringOrderPadLength = 10
   183  
   184  nameQuery := model.Equals("age", 22)
   185  // `StringOrderPadLength` is not needed to be specified for the query
   186  ```
   187  
   188  To turn off base32 encoding and keep the runes:
   189  
   190  ```go
   191  nameIndex.Base32Encode = false
   192  ```
   193  
   194  ## Unique indexes
   195  
   196  ```go
   197  emailIndex := model.ByEquality("email")
   198  emailIndex.Unique = true
   199  ```
   200  
   201  ## Design
   202  
   203  ### Restrictions
   204  
   205  To maintain all indexes properly, all fields must be filled out when saving.
   206  This sometimes requires a `Read, Modify, Write` pattern. In other words, partial updates will break indexes.
   207  
   208  This could be avoided later if model does the loading itself.
   209  
   210  ## TODO
   211  
   212  - Implement deletes
   213  - Implement counters, for pattern inspiration see the [tags service](https://github.com/micro/services/tree/master/blog/tags)
   214  - Test boolean indexes and its ordering
   215  - There is a stuttering in the way `id` fields are being saved twice. ID fields since they are unique do not need `id` appended after them in the record keys.