-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[Tables] Migration Guide #15857
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
[Tables] Migration Guide #15857
Changes from 1 commit
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
cc43e08
Migration Guide
joheredi 1c41568
Update sdk/tables/data-tables/MigrationGuide.md
joheredi b47e7b2
Update sdk/tables/data-tables/MigrationGuide.md
joheredi 189cc99
Update sdk/tables/data-tables/MigrationGuide.md
joheredi 1d2fa45
Update sdk/tables/data-tables/MigrationGuide.md
joheredi 4286fd8
Update sdk/tables/data-tables/MigrationGuide.md
joheredi e7279e7
Fix typos
joheredi a2d4ab6
Apply suggestions from code review
joheredi a79e02c
Address comments
joheredi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,387 @@ | ||
| # Guide for migrating to `@azure/data-tables` from `azure-storage` | ||
|
|
||
| This guide is intended to assist in the migration to `@azure/data-tables` from the legacy `azure-storage` package. It will focus on side-by-side comparisons for similar operations between the two packages. | ||
|
|
||
| We assume that you are familiar with `azure-storage`. If you are new to the Azure Tables client library for JavaScript, please refer to the [README](https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/tables/data-tables/README.md) and [samples](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/tables/data-tables/samples/v12) rather than this guide. | ||
|
|
||
| ## Table of contents | ||
joheredi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ## Migration benefits | ||
|
|
||
| As Azure has matured and been embraced by a more diverse group of developers, we have been focused on learning the patterns and practices to best support developer productivity and to understand the gaps that the JavaScript client libraries have. | ||
|
|
||
| There were several areas of consistent feedback expressed across the Azure client library ecosystem. One of the most important is that the client libraries for different Azure services have not had a consistent approach to organization, naming, and API structure. Additionally, many developers have felt that the learning curve was difficult, and the APIs did not offer a good, approachable, and consistent onboarding story for those learning Azure or exploring a specific Azure service. | ||
|
|
||
| To improve the development experience across Azure services, a set of uniform [design guidelines](https://azure.github.io/azure-sdk/general_introduction.html) was created for all languages to drive a consistent experience with established API patterns for all services. A set of [TypeScript & JavaScript Guidelines](https://azure.github.io/azure-sdk/typescript_introduction.html) was also introduced to ensure that TypeScript clients have a natural and idiomatic feel with respect to the TypeScript and JavaScript ecosystems. The new `@azure/data-tables` follows these guidelines. | ||
|
|
||
| ### Cross Service SDK improvements | ||
|
|
||
| The modern `@azure/data-tables` client library also provides the ability to share in some of the cross-service improvements made to the Azure development experience, such as | ||
|
|
||
| - Using the new `@azure/identity` library to share a single authentication approach between clients | ||
joheredi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| - A unified logging and diagnostics pipeline offering a common view of the activities across each of the client libraries | ||
| - Use of promises rather than callbacks for a simplified programming experience | ||
| - Use of async iterators in paging APIs | ||
|
|
||
| ## Important changes | ||
|
|
||
| ### Package name | ||
joheredi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| The modern client library is named `@azure/data-tables` and was released beginning with version 12. The legacy client library is named `azure-storage` with version of 2.x.x or below. | ||
|
|
||
| ### Constructing the clients | ||
|
|
||
| Previously in `azure-storage`, you would use `createTableService` which can be used to get an instance of the `TableService` in order to perform service level operations | ||
joheredi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ```javascript | ||
| const azure = require('azure-storage'); | ||
| var tableService = azure.createTableService("<connection-string>"); | ||
| ``` | ||
|
|
||
| Now, in `@azure/data-tables`, we need a TableServiceClient for service level operations. | ||
| ```javascript | ||
| const { TableServiceClient } = require('@azure/data-tables'); | ||
| const tableService = TableServiceClient.fromConnectionString("<connection-string>") | ||
| ``` | ||
|
|
||
| ### Creating a Table | ||
| Previously in `azure-storage`, we'd use a `TableService` instance to create a table. The `createTable` method would take a callback to execute once the table has been created. This forces sequential operations to be inside the callback, potentially creating a callback chain | ||
joheredi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ```javascript | ||
| const azure = require('azure-storage'); | ||
| var tableService = azure.createTableService("<connection-string>"); | ||
|
|
||
| const tableName = "<table-name>" | ||
| tableService.createTable(tableName); | ||
|
|
||
| ``` | ||
|
|
||
| With `@azure/data-tables` we have access to all table level operations directly from the `TableServiceClient`. Because the table service client is not affinitized to any one table, it is ideal for scenarios where you need to create, delete, or list more than one Table. | ||
joheredi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ```javascript | ||
| const { TableServiceClient, AzureNamedKeyCredential } = require('@azure/data-tables'); | ||
| const tableName = "<table-name>" | ||
| const tableClient = new TableServiceClient(tablesEndpoint, new AzureNamedKeyCredential("<accountName>","<accountKey>")); | ||
|
|
||
| // Creates the table with `tableName` if it doesn't exist | ||
| const table = await tableClient.createTable(tableName); | ||
| console.log(`The created table's name is ${table.name}.`) | ||
| ``` | ||
|
|
||
| If your intention is to work only in the context of a single table, it's also possible to create a table from the `TableClient`. | ||
|
|
||
| ```javascript | ||
| const { TableClient, AzureNamedKeyCredential } = require('@azure/data-tables'); | ||
| const tableName = "<table-name>" | ||
| const tableClient = new TableClient(tablesEndpoint, tableName, new AzureNamedKeyCredential("<accountName>","<accountKey>")); | ||
|
|
||
| // Creates the table with `tableName` if it doesn't exist | ||
| const table = await tableClient.createTable(); | ||
| console.log(`The created table's name is ${table.name}.`) | ||
| ``` | ||
|
|
||
| ### Adding data to the table | ||
| Previously in `azure-storage`, we would create our entity as an object with a specific structure for representing values, also keeping in mind that there are 2 required properties `PartitionKey` and `RowKey` in which the capital `P` and `R` respectively are important as the service is case sensitive. | ||
|
|
||
| There were 2 ways to set the property values in `azure-storage` the raw way in which the value of each property is an object with a property named `_`containing the value and an optional property named `$` to specify the `Edm` type. If no type is passed it is inferred | ||
|
|
||
| ```javascript | ||
| const azure = require('azure-storage'); | ||
| const tableName = "<table-name>" | ||
| const tableService = azure.createTableService("<connection-string>"); | ||
|
|
||
| const task1 = { | ||
| PartitionKey: {'_':'hometasks'}, | ||
| RowKey: {'_': '1'}, | ||
| description: {'_':'take out the trash'}, | ||
| dueDate: {'_':new Date(2015, 6, 20), '$':'Edm.DateTime'} | ||
| } | ||
|
|
||
| tableService.insertEntity(tableName, task1); | ||
| ``` | ||
|
|
||
| The other way in `azure-storage` to insert an entity was to use the entityGenerator which helped abstracting the creation of the value object described above | ||
|
|
||
| ```javascript | ||
| const azure = require('azure-storage'); | ||
| const entGen = azure.TableUtilities.entityGenerator; | ||
| const tableName = "<table-name>" | ||
| const tableService = azure.createTableService("<connection-string>"); | ||
|
|
||
| const task1 = { | ||
| PartitionKey: entGen.String('hometasks'), | ||
| RowKey: entGen.String('1'), | ||
| description: entGen.String('take out the trash'), | ||
| dueDate: entGen.DateTime(new Date(2015, 6, 20)), | ||
| } | ||
|
|
||
| tableService.insertEntity(tableName, task1); | ||
| ``` | ||
|
|
||
| Now in the new `@azure/data-tables` SDK, in order to have more idiomatic property names in our entities we have moved to `partitionKey` and `rowKey` (camel case). Also you no longer need to follow the value object structure or entityGenerator anymore. | ||
joheredi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ```javascript | ||
| const { TableClient, AzureNamedKeyCredential } = require('@azure/data-tables'); | ||
| const tableName = "<table-name>" | ||
| const tableClient = new TableClient(tablesEndpoint, tableName, new AzureNamedKeyCredential("<accountName>","<accountKey>")); | ||
|
|
||
| // Creates the table with `tableName` if it doesn't exist | ||
| const task1 = { | ||
| partitionKey: "hometasks", | ||
| rowKey: "1", | ||
| description: "take out the trash", | ||
| dueDate: new Date(2015, 6, 20), | ||
| } | ||
|
|
||
| await tableClient.createEntity(task1); | ||
| ``` | ||
|
|
||
| If you are using TypeScript, `@azure/data-tables` package provides a type that can help you build your entities providing static check to make sure the required `rowKey` and `partitionKey` properties are present. | ||
|
|
||
| ```typescript | ||
| const { TableClient, AzureNamedKeyCredential, TableEntity } = require('@azure/data-tables'); | ||
| const tableName = "<table-name>" | ||
| const tableClient = new TableClient(tablesEndpoint, tableName, new AzureNamedKeyCredential("<accountName>","<accountKey>")); | ||
|
|
||
| const task1: TableEntity = { | ||
| partitionKey: "hometasks", | ||
| rowKey: "1", | ||
| description: "take out the trash", | ||
| dueDate: new Date(2015, 6, 20), | ||
| } | ||
|
|
||
| await tableClient.createEntity(task1); | ||
| ``` | ||
|
|
||
| ### Fetching a single entity from the table | ||
| Both clients allow for fetching a single entity from the table if the partitionKey and rowKey are known. | ||
|
|
||
| Previously in `azure-storage`, to access the entity we'd need to do it inside the callback on `retrieveEntity` | ||
|
|
||
| ```javascript | ||
| const azure = require('azure-storage'); | ||
| var tableService = azure.createTableService("<connection-string>"); | ||
|
|
||
| const tableName = "<table-name>" | ||
| tableService.retrieveEntity(tableName, "hometasks", "1", function(error, result, response) { | ||
| if (!error) { | ||
| // result contains the entity | ||
| console.log(result); | ||
| } | ||
| }) | ||
| ``` | ||
|
|
||
| Now with `@azure/data-tables` we use `getEntity` on the TableClient, the return type is a Promise of the entity which can be awaited, making the code cleaner. | ||
|
|
||
| ```javascript | ||
| const { TableClient, AzureNamedKeyCredential } = require('@azure/data-tables'); | ||
| const tableName = "<table-name>" | ||
| const tableClient = new TableClient(tablesEndpoint, tableName, new AzureNamedKeyCredential("<accountName>","<accountKey>")); | ||
|
|
||
| const entity = await tableClient.getEntity("hometasks", "1"); | ||
| console.log(entity); | ||
| ``` | ||
|
|
||
| ### Querying data from the table | ||
| Previously in `azure-storage`, querying a table looked as follows, and didn't provide a built in way to handle pagination | ||
joheredi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ```javascript | ||
| const azure = require('azure-storage'); | ||
| const tableService = azure.createTableService("<connection-string>"); | ||
| const tableName = "<table-name>" | ||
| const query = new azure.TableQuery() | ||
| .where('PartitionKey eq ?', 'part2'); | ||
|
|
||
| let entities = []; | ||
|
|
||
| function listEntities(query, continuationToken, callback) { | ||
| tableService.queryEntities(tableName, query, function(error, result){ | ||
| entities.push(result.entities); | ||
| const token = result.continuationToken; | ||
| if(token) { | ||
| listEntities(query, continuationToken, callback) | ||
| } else { | ||
| console.log("completed getting all entities"); | ||
| callback(); | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| listEntities(query, null, function() { | ||
| console.log(entities); | ||
| }) | ||
|
|
||
| ``` | ||
|
|
||
| In the new `@azure/data-tables` we return a `PagedAsyncIterableIterator` that handles fetching all the pages internally, and can be iterated easily. | ||
joheredi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ```javascript | ||
| const { TableClient, AzureNamedKeyCredential, odata } = require('@azure/data-tables'); | ||
| const tableName = "<table-name>" | ||
| const tableClient = new TableClient(tablesEndpoint, tableName, new AzureNamedKeyCredential("<accountName>","<accountKey>")); | ||
| const partitionKey = "part2"; | ||
|
|
||
| const entities = tableClient.listEntities({ | ||
| queryOptions: { filter: odata`PartitionKey eq ${partitionKey}` } | ||
| }); | ||
|
|
||
| for await(const entity of entities) { | ||
| console.log(entity); | ||
| } | ||
|
|
||
| ``` | ||
|
|
||
| ### Delete table entities | ||
| Previously with `azure-storage`, deleting a table entity was accomplished with the following code. | ||
|
|
||
| ```javascript | ||
| const azure = require('azure-storage'); | ||
| var tableService = azure.createTableService("<connection-string>"); | ||
|
|
||
| const tableName = "<table-name>" | ||
| const task = { | ||
| PartitionKey: {'_':'hometasks'}, | ||
| RowKey: {'_': '1'} | ||
| }; | ||
|
|
||
| tableService.deleteEntity(tableName, task, function(error, response){ | ||
| if(!error) { | ||
| console.log("Entity deleted"); | ||
| } | ||
| }); | ||
|
|
||
| ``` | ||
|
|
||
| Now in `@azure/data-tables`, deleting an entity requires just the paritionKey and rowKey values. | ||
|
|
||
| ```javascript | ||
| const { TableClient, AzureNamedKeyCredential } = require('@azure/data-tables'); | ||
| const tableName = "<table-name>" | ||
| const tableClient = new TableClient(tablesEndpoint, tableName, new AzureNamedKeyCredential("<accountName>","<accountKey>")); | ||
|
|
||
| await tableClient.deleteEntity("hometasks", "1"); | ||
| console.log("Entity deleted") | ||
|
|
||
| ``` | ||
|
|
||
| ### Batch Transactions | ||
| Previously with `azure-storage`, creating and executing a transactional batch operation involved creating a `TableBatch` which contained the TableOperation(s) to be executed. The result from ExecuteBatch was a TableBatchResult which is essentially a collection of TableResults. | ||
|
|
||
| ```javascript | ||
| const azure = require('azure-storage'); | ||
| var tableService = azure.createTableService("<connection-string>"); | ||
|
|
||
| const tableName = "<table-name>" | ||
|
|
||
| const task1 = { | ||
| PartitionKey: {'_':'hometasks'}, | ||
| RowKey: {'_': '1'}, | ||
| description: {'_':'Take out the trash'}, | ||
| dueDate: {'_':new Date(2015, 6, 20)} | ||
| }; | ||
| const task2 = { | ||
| PartitionKey: {'_':'hometasks'}, | ||
| RowKey: {'_': '2'}, | ||
| description: {'_':'Wash the dishes'}, | ||
| dueDate: {'_':new Date(2015, 6, 20)} | ||
| }; | ||
|
|
||
| const batch = new azure.TableBatch(); | ||
|
|
||
| batch.iniertEntity(task1, {echoContent: true}) | ||
| batch.iniertEntity(task2, {echoContent: true}) | ||
|
|
||
| tableService.executeBatch(tableName, batch, function(error, result, response){ | ||
| if(!error) { | ||
| console.log("Batch completed"); | ||
| } | ||
| }); | ||
| ``` | ||
|
|
||
| Now in `@azure/data-tables`, you just need to create an array of operations and pass it to the `submitTransaction` method on the `TableClient` | ||
|
|
||
| ```javascript | ||
| const { TableClient, AzureNamedKeyCredential } = require('@azure/data-tables'); | ||
| const tableName = "<table-name>" | ||
| const tableClient = new TableClient(tablesEndpoint, tableName, new AzureNamedKeyCredential("<accountName>","<accountKey>")); | ||
|
|
||
| const task1 = { | ||
| PartitionKey: "hometasks", | ||
| RowKey: "1", | ||
| description: "Take out the trash", | ||
| dueDate: new Date(2015, 6, 20) | ||
| }; | ||
| const task2 = { | ||
| PartitionKey: "hometasks", | ||
| RowKey: "2", | ||
| description: "Wash the dishes", | ||
| dueDate: new Date(2015, 6, 20) | ||
| }; | ||
|
|
||
| const tableActions = [["create", task1], ["create", task2]]; | ||
|
|
||
|
|
||
| await tableClient.submitTransaction(tableActions); | ||
| console.log("Transaction completed") | ||
| ``` | ||
|
|
||
| For convenience also provide a helper that works in a similar way as `TableBatch` in `azure-storage`. | ||
|
|
||
| ```javascript | ||
| const { TableClient, AzureNamedKeyCredential, TableTransaction } = require('@azure/data-tables'); | ||
| const tableName = "<table-name>" | ||
| const tableClient = new TableClient(tablesEndpoint, tableName, new AzureNamedKeyCredential("<accountName>","<accountKey>")); | ||
|
|
||
| const task1 = { | ||
| PartitionKey: "hometasks", | ||
| RowKey: "1", | ||
| description: "Take out the trash", | ||
| dueDate: new Date(2015, 6, 20) | ||
| }; | ||
| const task2 = { | ||
| PartitionKey: "hometasks", | ||
| RowKey: "2", | ||
| description: "Wash the dishes", | ||
| dueDate: new Date(2015, 6, 20) | ||
| }; | ||
|
|
||
| const transaction = new TableTransaction(); | ||
| transaction.createEntity(task1); | ||
| transaction.createEntity(task2); | ||
|
|
||
| await tableClient.submitTransaction(transaction.actions); | ||
| console.log("Transaction completed") | ||
| ``` | ||
|
|
||
| ### Sequential Actions | ||
|
|
||
| Previously in `azure-storage`, all the operations took a callback used to execute actions in sequence. For example, to create a table and then insert two entities | ||
joheredi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ```javascript | ||
| const azure = require('azure-storage'); | ||
| var tableService = azure.createTableService("<connection-string>"); | ||
|
|
||
| const tableName = "<table-name>" | ||
| tableService.createTable(tableName, function() { | ||
| tableService.insertEntity(tableName, {PartitionKey: "p1", RowKey: "r1", foo: "bar"}, function() { | ||
| tableService.insertEntity(tableName, {PartitionKey: "p2", RowKey: "r2", foo: "baz"}); | ||
| }) | ||
| }) | ||
| ``` | ||
|
|
||
| With `@azure/data-tables` we work with promises which makes the programming experience better | ||
|
|
||
| ```javascript | ||
| const { TableClient, AzureNamedKeyCredential } = require('@azure/data-tables'); | ||
| const tableName = "<table-name>" | ||
| const tableClient = new TableClient(tablesEndpoint, tableName, new AzureNamedKeyCredential("<accountName>","<accountKey>")); | ||
|
|
||
| await tableClient.createTable(); | ||
| await tableClient.createEntity({partitionKey: "p1", rowKey: "r1", foo: "bar"}); | ||
| await tableClient.createEntity({partitionKey: "p2", rowKey: "r2", foo: "baz"}); | ||
|
|
||
| ``` | ||
|
|
||
| ## Additional samples | ||
|
|
||
| More examples can be found at [here](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/tables/data-tables/samples/v12) | ||
joheredi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.