Upgrade from v2 to v3#
We entirely rewrote the Go client but we chose to keep a similar design to make it as easy as possible to upgrade.
This new version is compatible with the same Go versions as before (from 1.8 up to the most recent Go version).
Dependency upgrade#
Because the package structure of the Go API client has changed between the v2
and v3, one cannot simply change the dependency version. The major change is
the removal of the algoliasearch/ sub-package in favor of algolia/search/
one. Hence, the first step before upgrading the package version is to replace
all algoliasearch/ imports and algoliasearch. package prefix with their
algolia/search/ and search. counterparts.
Since the introduction of versioned modules in Go 1.11, dependencies should be
retrieved automatically every time go build or go test is used.
However, if you would like to import a specific version of the Go client, use the following:
$
go get github.com/algolia/algoliasearch-client-go@vX.Y.Z
If you have not migrated yet to Go modules, and are still using dep as a
dependency manager, which is not the preferred way anymore, you can update
the package as usual:
1
2
3
4
5
6
7
8
# First change the `version` field of the
# `github.com/algolia/algoliasearch-client-go` constraint of your `Gopkg.toml`
# file to `3.X.Y` with your editor of choice.
vim Gopkg.toml
# Then run `dep` as follows to automatically update the `Gopkg.lock` file with
# the most recent minor version of the Algolia Go client.
dep ensure
Client Instantiations#
Search Client Instantiation#
Replace the instantiation of the search client as shown below.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Before
client := algoliasearch.NewClient("AJ0P3S7DWQ", "YourAPIKey")
index := client.InitIndex("your_index_name")
// After
client := search.NewClient("AJ0P3S7DWQ", "YourAPIKey")
index := client.InitIndex("your_index_name")
// Using configuration
client := search.NewClientWithConfig(search.Configuration{
AppID: "AJ0P3S7DWQ", // Mandatory
APIKey: "YourAPIKey", // Mandatory
Hosts: []string{ ... }, // Optional
Requester: customRequester, // Optional
ReadTimeout: 5 * time.Second, // Optional
WriteTimeout: 30 * time.Second, // Optional
Headers: map[string]string{ ... }, // Optional
})
Analytics Client Instantiation#
Replace the instantiation of the analytics client as shown below.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Before
client := algoliasearch.NewClient("AJ0P3S7DWQ", "YourAPIKey").InitAnalytics()
// After
client := analytics.NewClient("AJ0P3S7DWQ", "YourAPIKey")
// Using configuration
client := analytics.NewClientWithConfig(search.Configuration{
AppID: "AJ0P3S7DWQ", // Mandatory
APIKey: "YourAPIKey", // Mandatory
Hosts: []string{ ... }, // Optional
Requester: customRequester, // Optional
ReadTimeout: 5 * time.Second, // Optional
WriteTimeout: 30 * time.Second, // Optional
Region: region.US // Optional
Headers: map[string]string{ ... }, // Optional
})
Insights Client Instantiation#
Replace the instantiation of the analytics client as shown below.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Before
client := algoliasearch.NewClient("AJ0P3S7DWQ", "YourAPIKey").InitInsights()
// After
client := insights.NewClient("AJ0P3S7DWQ", "YourAPIKey")
userClient := client.User("UserToken")
// Using configuration
client := insights.NewClientWithConfig(search.Configuration{
AppID: "AJ0P3S7DWQ", // Mandatory
APIKey: "YourAPIKey", // Mandatory
Hosts: []string{ ... }, // Optional
Requester: customRequester, // Optional
ReadTimeout: 5 * time.Second, // Optional
WriteTimeout: 30 * time.Second, // Optional
Region: region.US // Optional
Headers: map[string]string{ ... }, // Optional
})
Functional options as optional parameters#
One of the biggest change is the addition of functional options, as coined by Dave Cheney to ease the manipulation of Algolia parameters.
For instance, when searching through some index records, instead of passing
parameters via a custom algoliasearch.Map, you can now use the
following syntax:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Before
index.Search(
"query",
algoliasearch.Map{
"attributesToRetrieve": []string{"objectID", "title"},
"hitsPerPage": 3,
},
})
// After
index.Search("query",
opt.AttributesToRetrieve("objectID", "title"),
opt.HitsPerPage(3),
)
It gives the advantage of better discoverability and improved type-safety as all options are now correctly typed.
Removal of algoliasearch.Object and algoliasearch.Map.#
The tedious part of migrating from v2 to v3 is to remove all
algoliasearch.Map and algoliasearch.Object references. Those objects were
mere aliases on top of map[string]interface{} which were added to handle
known types internally. However, from a user perspective, this was far from
great. All user structures needed to be transformed into those custom types.
Since this v3, user defined structures are now first-class citizens.
As an example, take this before/after snippet of code explaining how to save an object to an Algolia index:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type Book struct {
Title string `json:"title"`
Year string `json:"year"`
ObjectID string `json:"objectID"`
}
book := Book{
Title: "It",
Year: 1986,
ObjectID: "12345",
}
// Before
object := algoliasearch.Object{
"title": book.Title,
"year": book.Year,
"objectID": book.ObjectID,
}
index.AddObject(object)
// After
index.SaveObject(book)
Now, because objects are user-defined structures, the Go API client simply
cannot return them from functions, due to the lack of generics. The approach
that was taken is the same as the json.Unmarshal function from the standard
library: for any function which may return a user-defined object, an extra
parameter is now expected to be passed as a reference.
Again, as an example, take this before/after snippet of code explaining how to retrieve a deserialized object from an Algolia index:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Book struct {
Title string `json:"title"`
Year string `json:"year"`
ObjectID string `json:"objectID"`
}
// Before:
// the returned object is an `algoliasearch.Object`.
obj, err := index.GetObject(book.ObjectID)
fmt.Println(obj["title"])
// After:
// the object contained in the response payload is unmarshalled directly into
// the given reference, here an instance of a `Book` structure
var b Book
err := index.GetObject(book.ObjectID, &b)
fmt.Println(b.Title)
Finally, note that Go maps and any serializable object can actually be used.
Setting and Getting Settings#
Using the v2, handling settings was not convenient. This difficulty was
mainly due to GetSettings returning a Settings structure whereas
SetSettings was expecting an algoliasearch.Map (alias for
map[string]interface{}).
Because of this, a Settings.ToMap function was implemented to let users
obtain a algoliasearch.Map to be used by SetSettings. However,
algoliasearch.Map could not be easily converted into Settings. Also, both
objects could not be easily compared.
In the v3, we completely get rid of the algoliasearch.Map representation
for the settings, which lets you now change GetSettings and SetSettings as
such:
1
2
3
4
5
6
7
8
9
10
// Before
settings, err := index.GetSettings()
settingsAsMap := settings.ToMap()
settingsAsMap["replicas"] = []string{"replica1", "replica2"}
index.SetSettings(settingsAsMap)
// After
settings, err := index.GetSettings()
settings.Replicas = opt.Replicas("replica1", "replica2")
index.SetSettings(settings)
Waitable responses#
To wait for indexing operation to complete, the only solution was to explicitly
call index.WaitTask() with the appropriate response’s TaskID field. This
was not difficult but one had to know the existence of the TaskID field and
to learn how it plays with index.WaitTask.
More importantly, when waiting for multiple tasks to complete, there was no easy way of waiting concurrently on the different tasks.
Finally, most of other methods were not easily “waitable”. For instance, no solution was provided to wait for the completion of key or analytics-related operations.
Since v3, all response objects triggering asynchronous operations on Algolia’
side now have a Wait() method. Each method hides its own logic on
how to properly wait for its own underlying operation to complete.
To address the last issue, being able to wait on multiple tasks concurrently, a
new object has been added: wait.Group. It can be used as such:
1
2
3
4
5
6
7
8
9
10
11
12
g := wait.Group()
res1, err := index.SaveObjects(...)
g.Collect(res1)
res2, err := index.SetSettings(...)
res3, err := index.SaveRules(...)
g.Collect(res2, res3)
// `g.Wait()` call will block until both the `SaveObjects`, `SetSettings` and
// `SaveRules` operations terminates.
err = g.Wait()
Whenever operations are close to each other, wait.Wait(...) is also provided
as a shortcut.
1
2
3
4
5
res1, err := index.SaveObjects(...)
res2, err := index.SetSettings(...)
res3, err := index.SaveRules(...)
err = wait.Wait(res1, res2, res3)
New debug package#
This release also replaces the previous debug approach, which was using
ALGOLIA_DEBUG environment variable values to control global level of debug
messages.
Since v3, specific places of the code can now be guarded by
debug.Enable()/debug.Disable() calls to pretty-print raw JSON request and
response payloads and other specific debug information to the user. It can be
used as follows:
1
2
3
debug.Enable()
res, err := index.SaveObject(map[string]string{"objectID": "one"})
debug.Disable()
Which would print on the standard output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
> ALGOLIA DEBUG request:
method="POST"
url="https://YourApplicationID.algolia.net/1/indexes/test"
body=
{
"objectID": "one"
}
> ALGOLIA DEBUG response:
body=
{
"createdAt": "2019-04-12T15:57:21.669Z",
"taskID": 11054870692,
"objectID": "one"
}
New methods#
Client.MultipleBatch: perform batches across different indices (previouslyClient.Batch)Client.MultipleGetObjects: retrieve objects by objectID across different indicesIndex.BrowseObjects: produce an iterator over all objects matching the query parametersIndex.BrowseRules: produce an iterator over all rules matching the query parametersIndex.BrowseSynonyms: produce an iterator over all synonyms matching the query parameters
Removed methods#
*: replace withopt.ExtraHeader(...)oropt.ExtraURLParam(...)as functional optionsAnalyticsClient.WaitTask: replace by invoking response objects’.Waitmethod directly (see the Waitable responses section for more information)Client.AddUserKey: replace withClient.AddAPIKeyClient.Batch: replace withClient.MultipleBatchClient.ClearIndex: replace withIndex.ClearClient.DeleteIndex: replace withIndex.DeleteClient.DeleteUserKey: replace withClient.DeleteAPIKeyClient.GetStatus: replace withIndex.GetStatusClient.GetUserKey: replace withClient.GetAPIKeyClient.InitAnalytics: replace withanalytics.NewClientClient.InitInsights: replace withinsights.NewClientClient.ListKeys: replace withClient.ListAPIKeysClient.SetAnalyticsTimeout: replace withcontext.WithTimeoutas a functional optionClient.SetExtraHeader: replace withopt.ExtraHeaderas a functional optionClient.SetHTTPClient: replace with properRequesterinsearch.NewClientWithConfigClient.SetMaxIdleConnsPerHosts: replace with properRequesterinsearch.NewClientWithConfigClient.SetReadTimeout: replace withcontext.WithTimeoutas a functional optionClient.SetTimeout: replace withcontext.WithTimeoutas a functional optionClient.SetWriteTimeout: replace withcontext.WithTimeoutas a functional optionClient.UpdateUserKey: replace withClient.AddAPIKeyClient.WaitTask: replace by invoking response objects’.Waitmethod directly (see the Waitable responses section for more information)Index.AddAPIKey: replace withClient.AddAPIKeyIndex.AddObject: replace withIndex.SaveObjectIndex.AddObject: replace withIndex.SaveObjectIndex.AddObjects: replace withIndex.SaveObjects(..., opt.AutoGenerateObjectIDIfNotExist(true))Index.AddSynonym: replace withIndex.SaveSynonymIndex.AddUserKey: replace withClient.AddAPIKeyIndex.BatchRules: replace withIndex.SaveRulesIndex.BatchSynonyms: replace withIndex.SaveSynonymsIndex.BrowseAll: replace withIndex.BrowseObjectsIndex.Browse: replace withIndex.BrowseObjectsIndex.Copy: replace withClient.CopyIndex.DeleteAPIKey: replace withClient.DeleteAPIKeyIndex.DeleteByQuery: replace withIndex.DeleteByIndex.DeleteUserKey: replace withClient.DeleteAPIKeyIndex.GetAPIKey: replace withClient.GetAPIKeyIndex.GetObjectsAttrs: replace withIndex.GetObjects(..., opt.AttributesToRetrieve(...))Index.GetUserKey: replace withClient.GetAPIKeyIndex.ListKeys: replace withClient.ListAPIKeysIndex.MoveTo: replace withClient.MoveIndex.Move: replace withClient.MoveIndex.PartialUpdateObjectNoCreate: replace withIndex.PartialUpdateObject(..., opt.CreateIfNotExists(false))Index.PartialUpdateObjectsNoCreate: replace withIndex.PartialUpdateObjects(..., opt.CreateIfNotExists(false))Index.ScopedCopy: replace withClient.Copy(..., opt.Scopes(...))Index.SearchFacet: replace withIndex.SearchForFacetValuesIndex.UpdateAPIKey: replace withClient.UpdateAPIKeyIndex.UpdateObject: replace withIndex.SaveObjectIndex.UpdateObjects: replace withIndex.SaveObjectsIndex.UpdateUserKey: replace withClient.UpdateAPIKey