Object model surface area and nomenclature changes (v2)
See original GitHub issueGoals
For each of the objects we operate on (database, collection, document, etc.), we want to move their operations off of DocumentClient
and onto objects which represent those operations. We also want to rename those objects to better capture their functionality in today’s Cosmos terms (as opposed to legacy Document DB terms). We want the SDK to be easy to use & grok (understand).
Nomenclature updates
Current | New | Notes |
---|---|---|
DocumentDB | Cosmos | |
DocumentClient | CosmosClient | |
DatabaseAccount | DatabaseAccount | No Change |
Database | Database | No Change |
User | ? | Still in discussion |
User.* | ? | Still in discussion |
Collection | Container | |
Document | Item | |
Attachment | Attachment | No Change |
Conflict | ? | |
Feed | ? | |
Offers | ? | |
PartitionKeyRanges | ? | |
Stored Procedures | ? | |
Triggers | ? | |
User Defined Functions | ? |
Surface Area updates
A few notable changes:
- We’ll be moving the operations for a given object to the parent of that object. (aka
container
will have operations forreadItem
on it). - We’ll have a “fluent” style API which will replace the current URI based model of referencing objects. (aka
await client.getDatabase("foo").getContainer("bar").readItem("baz")
) - “builder” operations (basically
get
operations) will be sync and not talk to server - QCRRUD operations will be async (and will talk to the server)
We take some inspiration from REST models, but have a goal of being easy to understand/code versus being technically pure.
Examples
Query Items
const {result: items} = await client
.getDatabase(databaseName)
.getContainer(containerName)
.queryItems("select * from C")
.toArray();
Note: Might want to support queryItems<T> (for interfaces) to allow users to get help on the interface of items
.
Pass a container object around
Register collection with DI framework
ioc.register("todoCollection", client.getDatabase(databaseName).getContainer(containerName));
Consume container in route logic
public async getTodos(@Injectable("todoContainer") Container todoContainer) {
const {result: todos} = await todoContainer.readItems();
return todos;
}
Objects
Heirarchy
-
A given object can have many children
- “A database has collections” (1:N)
-
A given object only has 1 parent
- “A collection has a database” (1:1)
-
client
- database account
- offer
- databases
- database
- users
- user
- permissions
- permission
- permissions
- user
- containers -container - items - item - attachments - attachment - triggers - trigger - user defined functions - user defined function - stored procedures - stored procedure
Overall pattern will be:
const parent // any given parent object
parent.children.query // query many
parent.children.read //read many
parent.child("foo").read //read one
parent.child("foo").replace // replace one
parent.child.parent
CosmosClient
#.getDatabaseAccount(options?: RequestOptions) => Promise<DatabaseAccount>
#.databases: Databases
#.offers: Offers
DatabaseAccount
???
Databases
#.getDatabase(id: string) => Database
#.query(query: string | SqlQuerySpec, options?: FeedOptions) => QueryIterator<DatabaseDefinition>
#.create(body: object, options?: RequestOptions) => Promise<Response<DatabaseDefinition>>
#.read(options?: FeedOptions) => QueryIteratory<DatabaseDefinition>
- TBD: Worried about confusion with
getDatabase
- TBD: Worried about confusion with
Database
#.id
#.containers: Containers
#.read(options?: RequestOptions) => Promise<Response<DatabaseDefinition>>
#.replace(options?: RequestOptions) => Promise<Response<DatabaseDefinition>>
#.delete(options?: RequestOptions) => Promise<Response<DatabaseDefinition>>
Containers
#.getContainer(id: string) => Container
#.query(query: string | SqlQuerySpec, options?: FeedOptions) => QueryIterator<ContainerDefinition>
#.create(body: object, options?: RequestOptions) => Promise<Response<ContainerDefinition>>
#.read(options?: FeedOptions) => QueryIterator<ContainerDefinition>
Container
#.id
#.database: Database
#.items: Items
#.read(name: string, options?: RequestOptions) => Promise<Response<ContainerDefinition>>
#.replace(name: string, body: ContainerDefinition, options?: RequestOptions) => Promise<Response<ContainerDefinition>>
#.delete(name: string, options?: RequestOptions) => Promise<Response<ContainerDefinition>>
Items
#.getItem(id: string, pk?: string) => Item
#.query(query: string | SqlQuerySpec, options?: FeedOptions) => QueryIterator<?>
#.read(options?: FeedOptions) => QueryIterator<?>
#.create<T>(body: T, options?: RequestOptions) => Promise<Response<T>>
#.upsert<T>(body: T, options?: RequestOptions) => Promise<Response<T>>
Item
#.id
#.primaryKey
#.container: Container
#.readItem<T>(id: string, options?: RequestOptions) => Promise<Response<T>>
#.replaceItem<T>(id: string, body: T, options?: RequestOptions) => Promise<Response<T>>
- Do we need this id? How do we sniff out if we’re using a partition resolver?#.deleteItem<T>(id: string, options?: ResquestOptions) => Promise<Response<T>>
StoredProcedures
#.getStoredProcedure(id: string) => StoredProcedure
#.query(query: string | SqlQuerySpec, options?: FeedOptions) => QueryIterator<StoredProcedureDefinition>
#.read(options?: FeedOptions) => QueryIterator<StoredProcedureDefinition>
#.create(body: StoredProcedureDefinition, options?: RequestOptions) => Promise<Response<StoredProcedureDefinition>>
#.upsert(body: StoredProcedureDefinition, options?: RequestOptions) => Promise<Response<StoredProcedureDefinition>>
StoredProcedure
#.id
#.container: Container
#.read(options?: RequestOptions) => Promise<Response<StoredProcedureDefinition>>
#.replace(body: StoredProcedureDefinition, options?: RequestOptions) => Promise<Response<StoredProcedureDefinition>>
#.delete(options?: RequestOptions) => Promise<Response<StoredProcedureDefinition>>
#.execute<T>(params?: any[], options?: RequestOptions) => Promise<Response<T>>
Triggers
#.getTrigger(id: string) => Trigger
#.query(query: string | SqlQuerySpec, options?: FeedOptions) => QueryIterator<TriggerDefinition>
#.read(options?: FeedOptions) => QueryIterator<TriggerDefinition>
#.create(body: TriggerDefinition, options?: RequestOptions) => Promise<Response<TriggerDefinition>>
#.upsert(body: TriggerDefinition, options?: RequestOptions) => Promise<Response<TriggerDefinition>>
Trigger
#.id
#.container: Container
#.read(options?: RequestOptions) => Promise<Response<TriggerDefinition>>
#.replace(body: TriggerDefinition, options?: RequestOptions) => Promise<Response<TriggerDefinition>>
#.delete(options?: RequestOptions) => Promise<Response<TriggerDefinition>>
UserDefinedFunctions
#.getUserDefinedFunction(id: string) => UserDefinedFunction
#.query(query: string | SqlQuerySpec, options?: FeedOptions) => QueryIterator<UserDefinedFunctionDefinition>
#.read(options?: FeedOptions) => QueryIterator<UserDefinedFunctionDefinition>
#.create(body: UserDefinedFunctionDefinition, options?: RequestOptions) => Promise<Response<UserDefinedFunctionDefinition>>
#.upsert(body: UserDefinedFunctionDefinition, options?: RequestOptions) => Promise<Response<UserDefinedFunctionDefinition>>
UserDefinedFunction
#.id
#.container: Container
#.read(id: string, options?: RequestOptions) => Promise<Response<UserDefinedFunctionDefinition>>
#.replace(id: string, body: UserDefinedFunctionDefinition, options?: RequestOptions) => Promise<Response<UserDefinedFunctionDefinition>>
#.delete(id: string, options?: RequestOptions) => Promise<Response<UserDefinedFunctionDefinition>>
Offers
#.getOffer(id: string) => Offer
#.queryOffers(query: string | SqlQuerySpec, options?: FeedOptions) => QueryIterator<OfferDefinition>
#.readOffers(options?: FeedOptions) => QueryIterator<OfferDefinition>
Offer
#.id
#.readOffer(id: string, options?: RequestOptions) => Promise<Response<OfferDefinition>>
#.replaceOffer(body: OfferDefinition, options?: RequestOptions) => Promise<Response<OfferDefinition>>
User
#.id
- ???
Open Questions
-
Most of the API is top down, but can we put bottom up calls where it makes sense?
For instance,
client.readDatabase("foo")
instead beingclient.database("foo").read()
-
What are we doing with Users?
-
Are we going to continue to support partition resolver?
Change Log
- 2018-06-13: Modified the API structure to isolate operations to their representative objects
Issue Analytics
- State:
- Created 5 years ago
- Comments:16 (15 by maintainers)
Assuming that there is not another contending name, I prefer “collection” over “container”. Container I have mostly seen in a GUI context. It does feel awkward.
This feedback has been addressed or moved to independent issues, so I’m closing.