{"id":37175798,"url":"https://github.com/io-da/query","last_synced_at":"2026-01-14T20:31:19.967Z","repository":{"id":144210738,"uuid":"190565772","full_name":"io-da/query","owner":"io-da","description":"A query bus to fetch all the things.","archived":false,"fork":false,"pushed_at":"2023-09-27T08:11:16.000Z","size":41,"stargazers_count":4,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2023-09-27T16:42:08.809Z","etag":null,"topics":["bus","go","golang","query","query-bus","querybus"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/io-da.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2019-06-06T10:53:30.000Z","updated_at":"2023-09-26T12:23:41.000Z","dependencies_parsed_at":null,"dependency_job_id":"a684db69-e254-408e-b962-6cc97ce4c1e4","html_url":"https://github.com/io-da/query","commit_stats":null,"previous_names":[],"tags_count":12,"template":null,"template_full_name":null,"purl":"pkg:github/io-da/query","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/io-da%2Fquery","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/io-da%2Fquery/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/io-da%2Fquery/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/io-da%2Fquery/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/io-da","download_url":"https://codeload.github.com/io-da/query/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/io-da%2Fquery/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28434466,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T18:57:19.464Z","status":"ssl_error","status_checked_at":"2026-01-14T18:52:48.501Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bus","go","golang","query","query-bus","querybus"],"created_at":"2026-01-14T20:31:19.251Z","updated_at":"2026-01-14T20:31:19.953Z","avatar_url":"https://github.com/io-da.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# [Go](https://golang.org/) Query Bus\nA query bus to fetch all the things.  \n\n[![Maintainability](https://api.codeclimate.com/v1/badges/7e612d86b1e8bbe89858/maintainability)](https://codeclimate.com/github/io-da/query/maintainability)\n[![Test Coverage](https://api.codeclimate.com/v1/badges/7e612d86b1e8bbe89858/test_coverage)](https://codeclimate.com/github/io-da/query/test_coverage)\n[![GoDoc](https://godoc.org/github.com/io-da/query?status.svg)](https://godoc.org/github.com/io-da/query)\n\n## Installation\n``` go get github.com/io-da/query ```\n\n## Overview\n1. [Queries](#Queries)\n2. [Handlers](#Handlers)\n3. [Result](#Result)\n4. [Iterator Handlers](#Iterator-Handlers)\n5. [Iterator Result](#Iterator-Result)\n6. [Error Handlers](#Error-Handlers)\n   1. [Available Errors](#Available-Errors)\n6. [Cache Adapters](#Cache-Adapters)\n7. [The Bus](#The-Bus)  \n   1. [Tweaking Performance](#Tweaking-Performance)  \n   2. [Shutting Down](#Shutting-Down)  \n   3. [Available Errors](#Available-Errors)\n8. [Benchmarks](#Benchmarks)\n9. [Examples](#Examples)\n\n## Introduction\nThis library is intended for anyone looking to query for data in a decoupled architecture. **No reflection, no closures.**\n\n## Getting Started\n\n### Queries\nQueries are any type that implements the _Query_ interface. Ideally they should contain immutable data.  \n```go\ntype Query interface {\n    ID() []byte\n}\n```\n\nQueries can optionally implement the _Cacheable_ interface for builtin caching.  \n```go\ntype Cacheable interface {\n    CacheKey() []byte\n    CacheDuration() time.Duration\n}\n```\n\n### Handlers\nHandlers are any type that implements the _Handler_ interface. Handlers must be instantiated and provided to the bus using the ```bus.Handlers``` function.  \n```go\ntype Handler interface {\n    Handle(qry Query, res *Result) error\n}\n```\nHandlers _catch_ the query (stop propagation) whenever they explicitly use ```res.Done()```. Otherwise the query will be provided to all the handlers that expect it. This strategy can be used to have multiple fallback handlers for the same query or have the _Result_ be populated by multiple handlers.  \nWhenever a query fails to be handled, the bus will throw an error. **A query is considered handled whenever any data is provided to the result or when the function ```res.Handled()``` is explicitly used.**\n\n### Result\nResult is the _struct_ returned from ```bus.Query```. This is where the data fetched will reside.  \nThe handlers provide the data to the result using the functions ```res.Add``` or ```res.Set```.  \nThis data can then be retrieved by using the the functions ```res.First``` (to retrieve only the first result) or ```res.All``` (to return the whole data slice).  \n\n### Iterator Handlers\nIterator handlers are any type that implements the _IteratorHandler_ interface. Iterator handlers must be instantiated and provided to the bus using the ```bus.InitializeIteratorHandlers``` function.  \n```go\ntype IteratorHandler interface {\n    Handle(qry Query, res *IteratorResult) error\n}\n```\nThese behave nearly identical to normal handlers. However there are a couple of differences:  \n - Expect an _IteratorResult_ instead of _Result_.\n - **Can not be cached**.\n \nIterator handlers are intended to be used with large sets of data. Providing a possibility to iterate over the data without additional preloading.  \n\n### Iterator Result\nIteratorResult is the _struct_ returned from ```bus.IteratorQuery```. This struct acts as a proxy between the handlers and the consumer.  \nThe handlers provide the data to the result using the function ```res.Yield```.  \nThis data can then be processed while being populated using the the function ```res.Iterate```.  \n\n### Error Handlers\nError handlers are any type that implements the _ErrorHandler_ interface. Error handlers are optional (but advised) and provided to the bus using the ```bus.ErrorHandlers``` function.  \n```go\ntype ErrorHandler interface {\n    Handle(qry Query, err error)\n}\n```\nAny time an error occurs within the bus, it will be passed on to the error handlers. This strategy can be used for decoupled error handling.\n\n#### Available Errors\nBelow is a list of errors that can occur.  \n\n```go\n// query.InvalidQueryError  \n// query.QueryBusNotInitializedError\n// query.QueryBusIsShuttingDownError\n// query.ErrorNoQueryHandlersFound\n// query.ErrorQueryTimedOut\n\ntype errorHandler struct {}\nfunc (e errorHandler) Handle(qry Query, err error) {\n    switch(err.(type)) {\n        case query.InvalidQueryError:\n            // do something\n        case query.QueryBusNotInitializedError:\n            // do something\n        case query.QueryBusIsShuttingDownError:\n            // do something\n        case query.ErrorQueryTimedOut, query.ErrorNoQueryHandlersFound:\n            // do something\n        default:\n            // do something\n    }\n}\nbus.ErrorHandlers(errorHandler)\n\n```\n\n### Cache Adapters\nCache adapters are any type that implements the _CacheAdapter_ interface. Cache adapters are optional (but advised) and provided to the bus using the ```bus.CacheAdapters``` function.  \n```go\ntype CacheAdapter interface {\n    Set(qry Cacheable, res *Result) bool\n    Get(qry Cacheable) *Result\n    Expire(qry Cacheable)\n    Shutdown()\n}\n```\nJust as the query handlers, this approach allows the usage of different cache adapters for different query types.  \nIf the cache adapter returns ```true``` on ```Set``` the bus will assume the result was successfully cached.  \n**On retrieval the bus will return the results from the first adapter that returns data for the given query. The order of the adapters is always respected.**  \nBy default the bus comes with a _MemoryCacheAdapter_. This adapter will cache the results in memory and supports duration specification on the order of microseconds (accuracy depends on server load). Expired results will be automatically cleared from memory.    \n\n### The Bus\n_Bus_ is the _struct_ that will be used for all the application's queries.  \nThe _Bus_ should be instantiated (```NewBus()```) and initialized(```bus.InitializeIteratorHandlers```) on application startup.  \nThe initialization is only required for iterator queries and is separated from the instantiation for dependency injection purposes.  \nThe application should instantiate the _Bus_ once and then use it's reference for all the queries.  \n**The order in which the handlers are provided to the _Bus_ is always respected. This is the order used when propagating queries.**\n\n#### Tweaking Performance\nThe number of workers for iterator queries can be adjusted.\n```go\nbus.IteratorWorkerPoolSize(10)\n```\nIf used, this function **must** be called **before** the call to ```bus.InitializeIteratorHandlers```. And it specifies the number of [goroutines](https://gobyexample.com/goroutines) used to handle iterator queries.  \nIn some scenarios increasing this value can drastically improve performance.  \nIt defaults to the value returned by ```runtime.GOMAXPROCS(0)```.  \n  \nThe buffer size of the iterator query queue can also be adjusted.  \nDepending on the use case, this value may greatly impact performance.\n```go\nbus.IteratorQueueBuffer(100)\n```\nIf used, this function **must** be called **before** the call to ```bus.InitializeIteratorHandlers```.  \nIt defaults to 100.  \n  \nThe buffer size of the iterator results channel can also be adjusted.  \nDepending on the use case, this value may greatly impact performance.\n```go\nbus.IteratorResultBuffer(0)\n```\nIf used, this function **may** be called **before** any iterator query is performed.  \nIt defaults to 0.  \n\n#### Shutting Down\nThe _Bus_ also provides a shutdown function that attempts to gracefully stop the query bus and all its routines.\n```go\nbus.Shutdown()\n```  \n**This function will block until the bus is fully stopped.**\n\n## Benchmarks\nThe query handler returns a single value for simulation purposes.  \n\n| Benchmark Type | Time |\n| :--- | :---: |\n| Queries | 201 ns/op |\n| IteratorQueries | 783 ns/op |  \n\nIterator queries add a small overhead and are not worth when used for small sets of data (also due to lack of caching). They are better suited to iterate over large sets of data while avoiding preloading.\n\n## Examples\n\n#### Example Queries\nA ```struct``` query.\n```go\ntype Foo struct {\n    bar string\n}\nfunc (*Foo) ID() []byte {\n    return []byte(\"FOO-UUID\")\n}\n```\n\nA ```string``` query that also implements caching.\n```go\ntype Bar string\nfunc (Bar) ID() []byte {\n    return []byte(\"BAR-UUID\")\n}\nfunc (Bar) CacheKey() []byte {\n    return []byte(\"BAR-CACHE-KEY\")\n}\nfunc (Bar) CacheDuration() time.Duration {\n    return time.Minute * 5\n}\n```\n\n#### Example Handlers\nA query handler that listens to multiple query types.\n```go\ntype FooBarHandler struct {\n}\n\nfunc (hdl *FooBarHandler) Handle(qry Query, res *Result) error {\n    // check the query type\n    switch qry := qry.(type) {\n    case *Foo:\n        // handler logic\n        res.Add(\"Bar\")\n    case Bar:\n        // handler logic\n        res.Add(\"Foo\")\n    }\n    return nil\n}\n```\n\nAn iterator query handler.\n```go\ntype FooBarIteratorHandler struct {\n}\n\nfunc (hdl *FooBarIteratorHandler) Handle(qry Query, res *IteratorResult) error {\n    // check the query type\n    switch qry := qry.(type) {\n    case *Foo:\n        // handler logic\n        res.Yield(\"Bar\")\n    }\n    return nil\n}\n```\n\n#### Putting it together\nInitialization and usage of the exemplified queries and handlers\n```go\nimport (\n    \"github.com/io-da/query\"\n)\n\nfunc main() {\n    // instantiate the bus (returns *query.Bus)\n    bus := query.NewBus()\n    \n    // provide the bus with all of the application's query handlers\n    bus.Handlers(\n        \u0026FooBarHandler{},\n    )\n    \n    // initialize the bus with all of the application's iterator query handlers\n    bus.InitializeIteratorHandlers(\n        \u0026FooBarIteratorHandler{},\n    )\n    \n    // query away!\n    res1, err := bus.Query(\u0026Foo{})\n    // get the first result only\n    val := res1.First() // \"Bar\"\n\n    res2, err := bus.Query(Bar(\"Bar\"))\n    // get all the results\n    vals := res2.All() // [\"Foo\"]\n\n    res3, err := bus.IteratorQuery(\u0026Foo{})\n    // range over the values, processing them while they are being populated\n    for val := range res3.Iterate() {\n        // do something with the val\n        // \"Bar\"\n    }\n}\n```\n\n## Contributing\nPull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.\n\nPlease make sure to update tests as appropriate.\n\n## License\n[MIT](https://choosealicense.com/licenses/mit/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fio-da%2Fquery","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fio-da%2Fquery","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fio-da%2Fquery/lists"}