API Reference / API Methods / Upgrade guides for the API client
Sep. 26, 2019

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/v3@v3.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 (previously Client.Batch)
  • Client.MultipleGetObjects: retrieve objects by objectID across different indices
  • Index.BrowseObjects: produce an iterator over all objects matching the query parameters
  • Index.BrowseRules: produce an iterator over all rules matching the query parameters
  • Index.BrowseSynonyms: produce an iterator over all synonyms matching the query parameters

Removed methods

  • *: replace with opt.ExtraHeader(...) or opt.ExtraURLParam(...) as functional options
  • AnalyticsClient.WaitTask: replace by invoking response objects’ .Wait method directly (see the Waitable responses section for more information)
  • Client.AddUserKey: replace with Client.AddAPIKey
  • Client.Batch: replace with Client.MultipleBatch
  • Client.ClearIndex: replace with Index.Clear
  • Client.DeleteIndex: replace with Index.Delete
  • Client.DeleteUserKey: replace with Client.DeleteAPIKey
  • Client.GetStatus: replace with Index.GetStatus
  • Client.GetUserKey: replace with Client.GetAPIKey
  • Client.InitAnalytics: replace with analytics.NewClient
  • Client.InitInsights: replace with insights.NewClient
  • Client.ListKeys: replace with Client.ListAPIKeys
  • Client.SetAnalyticsTimeout: replace with context.WithTimeout as a functional option
  • Client.SetExtraHeader: replace with opt.ExtraHeader as a functional option
  • Client.SetHTTPClient: replace with proper Requester in search.NewClientWithConfig
  • Client.SetMaxIdleConnsPerHosts: replace with proper Requester in search.NewClientWithConfig
  • Client.SetReadTimeout: replace with context.WithTimeout as a functional option
  • Client.SetTimeout: replace with context.WithTimeout as a functional option
  • Client.SetWriteTimeout: replace with context.WithTimeout as a functional option
  • Client.UpdateUserKey: replace with Client.AddAPIKey
  • Client.WaitTask: replace by invoking response objects’ .Wait method directly (see the Waitable responses section for more information)
  • Index.AddAPIKey: replace with Client.AddAPIKey
  • Index.AddObject: replace with Index.SaveObject
  • Index.AddObject: replace with Index.SaveObject
  • Index.AddObjects: replace with Index.SaveObjects(..., opt.AutoGenerateObjectIDIfNotExist(true))
  • Index.AddSynonym: replace with Index.SaveSynonym
  • Index.AddUserKey: replace with Client.AddAPIKey
  • Index.BatchRules: replace with Index.SaveRules
  • Index.BatchSynonyms: replace with Index.SaveSynonyms
  • Index.BrowseAll: replace with Index.BrowseObjects
  • Index.Browse: replace with Index.BrowseObjects
  • Index.Copy: replace with Client.Copy
  • Index.DeleteAPIKey: replace with Client.DeleteAPIKey
  • Index.DeleteByQuery: replace with Index.DeleteBy
  • Index.DeleteUserKey: replace with Client.DeleteAPIKey
  • Index.GetAPIKey: replace with Client.GetAPIKey
  • Index.GetObjectsAttrs: replace with Index.GetObjects(..., opt.AttributesToRetrieve(...))
  • Index.GetUserKey: replace with Client.GetAPIKey
  • Index.ListKeys: replace with Client.ListAPIKeys
  • Index.MoveTo: replace with Client.Move
  • Index.Move: replace with Client.Move
  • Index.PartialUpdateObjectNoCreate: replace with Index.PartialUpdateObject(..., opt.CreateIfNotExists(false))
  • Index.PartialUpdateObjectsNoCreate: replace with Index.PartialUpdateObjects(..., opt.CreateIfNotExists(false))
  • Index.ScopedCopy: replace with Client.Copy(..., opt.Scopes(...))
  • Index.SearchFacet: replace with Index.SearchForFacetValues
  • Index.UpdateAPIKey: replace with Client.UpdateAPIKey
  • Index.UpdateObject: replace with Index.SaveObject
  • Index.UpdateObjects: replace with Index.SaveObjects
  • Index.UpdateUserKey: replace with Client.UpdateAPIKey

Did you find this page helpful?