Upgrade guides for the API client
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("YourApplicationID", "YourAPIKey")
index := client.InitIndex("your_index_name")
// After
client := search.NewClient("YourApplicationID", "YourAPIKey")
index := client.InitIndex("your_index_name")
// Using configuration
client := search.NewClientWithConfig(search.Configuration{
AppID: "YourApplicationID", // 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("YourApplicationID", "YourAPIKey").InitAnalytics()
// After
client := analytics.NewClient("YourApplicationID", "YourAPIKey")
// Using configuration
client := analytics.NewClientWithConfig(search.Configuration{
AppID: "YourApplicationID", // 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("YourApplicationID", "YourAPIKey").InitInsights()
// After
client := insights.NewClient("YourApplicationID", "YourAPIKey")
userClient := client.User("UserToken")
// Using configuration
client := insights.NewClientWithConfig(search.Configuration{
AppID: "YourApplicationID", // 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’.Wait
method directly (see the Waitable responses section for more information)Client.AddUserKey
: replace withClient.AddAPIKey
Client.Batch
: replace withClient.MultipleBatch
Client.ClearIndex
: replace withIndex.Clear
Client.DeleteIndex
: replace withIndex.Delete
Client.DeleteUserKey
: replace withClient.DeleteAPIKey
Client.GetStatus
: replace withIndex.GetStatus
Client.GetUserKey
: replace withClient.GetAPIKey
Client.InitAnalytics
: replace withanalytics.NewClient
Client.InitInsights
: replace withinsights.NewClient
Client.ListKeys
: replace withClient.ListAPIKeys
Client.SetAnalyticsTimeout
: replace withcontext.WithTimeout
as a functional optionClient.SetExtraHeader
: replace withopt.ExtraHeader
as a functional optionClient.SetHTTPClient
: replace with properRequester
insearch.NewClientWithConfig
Client.SetMaxIdleConnsPerHosts
: replace with properRequester
insearch.NewClientWithConfig
Client.SetReadTimeout
: replace withcontext.WithTimeout
as a functional optionClient.SetTimeout
: replace withcontext.WithTimeout
as a functional optionClient.SetWriteTimeout
: replace withcontext.WithTimeout
as a functional optionClient.UpdateUserKey
: replace withClient.AddAPIKey
Client.WaitTask
: replace by invoking response objects’.Wait
method directly (see the Waitable responses section for more information)Index.AddAPIKey
: replace withClient.AddAPIKey
Index.AddObject
: replace withIndex.SaveObject
Index.AddObject
: replace withIndex.SaveObject
Index.AddObjects
: replace withIndex.SaveObjects(..., opt.AutoGenerateObjectIDIfNotExist(true))
Index.AddSynonym
: replace withIndex.SaveSynonym
Index.AddUserKey
: replace withClient.AddAPIKey
Index.BatchRules
: replace withIndex.SaveRules
Index.BatchSynonyms
: replace withIndex.SaveSynonyms
Index.BrowseAll
: replace withIndex.BrowseObjects
Index.Browse
: replace withIndex.BrowseObjects
Index.Copy
: replace withClient.Copy
Index.DeleteAPIKey
: replace withClient.DeleteAPIKey
Index.DeleteByQuery
: replace withIndex.DeleteBy
Index.DeleteUserKey
: replace withClient.DeleteAPIKey
Index.GetAPIKey
: replace withClient.GetAPIKey
Index.GetObjectsAttrs
: replace withIndex.GetObjects(..., opt.AttributesToRetrieve(...))
Index.GetUserKey
: replace withClient.GetAPIKey
Index.ListKeys
: replace withClient.ListAPIKeys
Index.MoveTo
: replace withClient.Move
Index.Move
: replace withClient.Move
Index.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.SearchForFacetValues
Index.UpdateAPIKey
: replace withClient.UpdateAPIKey
Index.UpdateObject
: replace withIndex.SaveObject
Index.UpdateObjects
: replace withIndex.SaveObjects
Index.UpdateUserKey
: replace withClient.UpdateAPIKey