Docs / ArchitectureKit / Fundamentals / Defining Queries

Defining Queries

A query describes what someone wants to know. Define it as a struct, and answer it with a function that reads a view. To turn the items into a slice, use slices.Collect:

type ListBooks struct {
  OnlyAvailable bool
  Limit         int
}

func listBooks(catalog architecturekit.View[BookItem]) func(context.Context, ListBooks) ([]BookItem, error) {
  return func(ctx context.Context, q ListBooks) ([]BookItem, error) {
    items, err := catalog.All(ctx)
    if err != nil {
      return nil, err
    }

    // ...

    return slices.Collect(items), nil
  }
}

To filter, order, page, and transform the items, use the query package:

import "github.com/thenativeweb/architecturekit-golang/architecturekit/query"

All of its functions take an iterator, and those that return items return an iterator again, so they can be combined without collecting anything in between.

Filtering and Transforming Items#

To keep only some items, call the Where function with a function that selects them:

if q.OnlyAvailable {
  items = query.Where(items, func(item BookItem) bool {
    return !item.IsBorrowed
  })
}

To turn every item into something else, call the Select function:

titles := query.Select(items, func(item BookItem) string {
  return item.Title
})

Ordering Items#

To order items by a key, call the OrderBy function, or the OrderByDescending function for the reverse order:

items = query.OrderBy(items, func(item BookItem) string {
  return item.Title
})

To order items by a comparison function, for example by several fields, call the OrderByFunc function. It expects the same kind of function as slices.SortFunc, so cmp.Or and strings.Compare work with it:

items = query.OrderByFunc(items, func(left, right BookItem) int {
  return cmp.Or(
    strings.Compare(left.Author, right.Author),
    strings.Compare(left.Title, right.Title),
  )
})

Note that ordering reads all items, and that it is stable, so items that compare as equal keep their order.

Paging Items#

To skip a number of items, call the Skip function. To stop after a number of items, call the Take function:

if q.Limit > 0 {
  items = query.Take(items, q.Limit)
}

Both can be combined, for example to get the third page of ten items:

items = query.Take(query.Skip(items, 20), 10)

Getting a Single Item#

To get the first item, call the First function. It returns false if there is none:

first, ok := query.First(items)

To get the only item, call the Single function. It returns query.ErrNoItems if there is none, and query.ErrTooManyItems if there are several:

type GetBook struct {
  BookID string
}

func getBook(catalog architecturekit.View[BookItem]) func(context.Context, GetBook) (BookItem, error) {
  return func(ctx context.Context, q GetBook) (BookItem, error) {
    items, err := catalog.All(ctx)
    if err != nil {
      return BookItem{}, err
    }

    return query.Single(query.Where(items, func(item BookItem) bool {
      return item.ID == q.BookID
    }))
  }
}

Counting Items#

To count items, call the Count function. To check whether at least one item matches, call the Any function, which stops at the first match:

count := query.Count(items)

hasBorrowedBooks := query.Any(items, func(item BookItem) bool {
  return item.IsBorrowed
})