A guide for utilizing and implementing functionality in the Diamond Kinetics Java API. Please feel free to create pull requests in order to make updates.
- Concepts
- Naming Conventions
- Actions
- Non-standard Actions
- Filtering
- Sorting
- Searching
- Aliases
- Limiting Fields
- Pagination
- Relationship Auto Loading
- Batch Operations
- Versioning
- Error Handling
- White Space and Compression
- References
Concepts that are helpful to know in order to comprehend and use the API.
Response Code | Meaning | Diamond Kinetics Specific |
---|---|---|
200 | OK | Successful request & response |
201 | CREATED | Successful creation |
400 | BAD REQUEST | Validation failure |
401 | UNAUTHORIZED | There isn't enough information in the request to authenticate the user |
403 | FORBIDDEN | The authenticated user is unable to perform the requested action |
409 | CONFLICT | The object you are trying to create already exists. This is always for UUID conflict. |
All entities in the Diamond Kinetics API will have fixed UUIDs to reference externally. This is generally a good practice (citation needed) and limits leaking any information about row numbers/count of our tables.
- sensorCreated
- clientCreated & clientUpdated
- serverCreated & serverUpdated
Any user in the system is authorized to see the sessions of any other user if they fall into one of the following categories.
Any two connected users can see each others sessions.
Any admin of a group can see all sessions of any other user in the group.
- The "Session View Role" group property is the minimum role needed to view the sessions that are associated with this Group. Effectively, this prevents other users of the system from seeing sessions in this group. Most useful for groups created by showcase organizations
- The DATA_ACCESSOR role is a special role that allows the user to ONLY access the sessions that are associated with this Group and not sessions taken outside of the group
Resources in the Diamond Kinetics API should always be represented as nouns. When used in an endpoint, a resource should be plural.
All such resources should be represented in camelCase.
- To represent pitching session resources, we use:
/pitchingSessions
- To represent batting session resources, we use:
/battingSessions
We perform actions on resources, such as create, read, update, and delete (usually via an HTTP request type).
- To create a pitching session, we use:
POST /pitchingSessions
- To get a list of pitching sessions for the current user, we use:
GET /pitchingSessions
- To get a specific pitching session, we use:
GET /pitchingSessions/:uuid
PUT
orPATCH
can also be used in order to update or delete a specific resource as well.
When it comes to dealing with non-standard actions, we have a few choices available to us for URL mapping. [1]
- Restructure the action to appear like a field of a resource:
PUT /bullpens/:uuid/complete
- Treat the action like a sub-resource:
PUT /pitchingSessions/:uuid/flag
andDELETE /pitchingSessions/:uuid/flag
[5] - In the event there is no way to map an action to a sensible RESTful structure, using a generic endpoint is alright.
- A multi-resource search can use something such as
GET /search
- A multi-resource search can use something such as
Use a unique query parameter in the endpoint URL for each field that implements filtering. [1]
Generally, the Diamond Kinetics API only filters data based on the lastUpdated parameter. Generic filtering and slicing of data is something we don't do - we prefer strongly to understand the data access needs of our client and give custom accessors that match the business needs of the client.
- To only list flagged pitching sessions, we use:
GET /pitchingSessions?flagged=true
- To only list non-deleted pitching sessions, we use:
GET /pitchingSessions?deleted=false
To sort, we can use a generic parameter such as sort
for describing any sorting rules on an endpoint.
Sorting with multiple criteria can be accomplished using a list of comma separated rules.
[1]
- To list pitches from a pitching session sorted by release speed in descending order, we use
GET /pitchingSessions/:uuid/pitches?sort=-releaseSpeed
- To list pitches from a pitching session sorted by release speed in ascending order, we use
GET /pitchingSessions/:uuid/pitches?sort=releaseSpeed
Notice that sorting in a descending order, we use a -
before the property name, and for ascending we just use the
property name.
If we wanted to essentially combine filtering and sorting in order to create a search for resources, we could just combine query parameters. [1]
- To sort and filter pitches from a pitching session, we use
GET /pitchingSessions/:uuid/pitches?flagged=true&sort=-releaseSpeed
- This would retrieve flagged pitches and sort them by release speed in descending order.
Packaging sets of conditions into easily accessible paths can make API consumption a bit easier. If there are parameters
that are consistently used on a specific endpoint, it may make sense to create an alias for it. These will more often
than not be in the form of a GET
request.
- To retrieve our most recent pitching session, create an alias such as:
GET /pitchingSessions/latest
- To retrieve a group's current join requests, create an alias such as:
GET /groups/:uuid/joinRequests
Consumers of the API don't always need the full representation of a resource. The ability to select and choose returned
fields can let the consumer minimize network traffic and speed up their API usage. To accomplish this, we use a fields
query parameter that takes a comma separated list of fields to include in the response.
- To only retrieve the
uuid
andreleaseSpeed
fields when retrieving pitches for a particular pitching session, we use:GET /pitchingSessions/:uuid/pitches?fields=uuid,releaseSpeed
- To only retrieve the
uuid
,sessionDate
, andflagged
fields when retrieving a list of the current user's pitching sessions, we use:GET /pitchingSessions?fields=uuid,sessionDate,flagged
Including any pagination details for a list of resources should always be accomplished using the Link
header in the
API response. This can return a set of ready-made links so an API consumer does not have to worry about constructing
them on their own.
[1]
- For an API response from a request to list a current user's pitching sessions, the
Link
header for the first page looks like:
Link: <https://api.diamondkinetics.com/pitchingSessions?page=2&perPage=10>;
rel="next", <https://api.diamondkinetics.com/pitchingSessions?page=10&perPage=10>; rel="last"
- The second page
Link
header looks like:
Link: <https://api.diamondkinetics.com/pitchingSessions?page=1&perPage=10>; rel="previous",
<https://api.diamondkinetics.com/pitchingSessions?page=3&perPage=10>; rel="next",
<https://api.diamondkinetics.com/pitchingSessions?page=10&perPage=10>; rel="last"
It's common for an API consumer to need data related to a requested resource. The efficient way to accomplish this would be to allow related data to be returned along with the original requested resource. A good way to include related resources would be to use a query parameter named something along the lines of embed for children, and expand for parents. This query parameter utilizes a comma separated list for it's value which represents a list of fields to be embedded with the requested resource. Dot-notation can be used to reference sub-fields.
NOTE: Be wary of N+1 select scenarios when implementing this approach.
- Returning a user along with a pitching session can be accomplished with:
GET /pitchingSessions/:uuid?embed=user
- Returning just a user's first name, last name, and uuid along with the pitching session can be accomplished with
GET /pitchingSessions/:uuid?embed=user.firstName,user.lastName,user.uuid
- Returning a parent pitchingSession with a pitch can be accomplished with
GET /pitch/:uuid?expand=pitchingSession
- Returning a parent pitchingSession's name with a pitch can be accomplished with
GET /pitch/:uuid?expand=pitchingSession.name
-
When a single related resource is not auto-loaded, we will supply the
UUID
for it. Such asuserUuid
for aUser
resource. -
When a set of related resources are not auto-loaded, we will provide metadata about the set such as
count
,deletedCount
, andmaxLastUpdated
. -
If we aren't auto-loading related entities, do we include metadata about the related entity/entities.
- For a single entity, we would just supply the UUID such as
userUuid
. - For a set of entities, we will have an object that contains metadata such as
count
,deletedCount
, andmaxLastUpdated
. For example, a batting session structure without swings loaded:
{ uuid: string, created: string, lastUpdated: string, deleted: boolean, swings: { url: string, count: integer, deletedCount: integer, maxLastUpdated: string } }
- MJR 2020-07-07:
- Dropping URL since we're not doing any HATEOAS.
- Starting all count related parameters with "count" so they group together in API documentation.
- Introducing
countViewable
as an example property to facilitate objects like GroupMembership that will need to incorporate additional information to present an accurate count to the user. I can also see GroupMembership in particular addingcountPendingJoinRequests
,countPendingInvites
, etc. Lists of Swings would include things likecountFlagged
- in essence, consider this to be extendable per object type but withcountTotal
andcountDeleted
as standard properties.
{ uuid: string, created: string, lastUpdated: string, deleted: boolean, swings: { countTotal: integer, countDeleted: integer, countViewable: integer, maxLastUpdated: string } }
- If resources are loaded, they will be contained in an array named
data
. For example, a batting session with swings loaded.
{ uuid: string, created: string, lastUpdated: string, deleted: boolean, swings: { url: string, data: array, count: integer, deletedCount: integer, maxLastUpdated: string } }
- For a single entity, we would just supply the UUID such as
Sometimes we want to perform a batch operation such as GET
multiple users, GET
batting sessions for multiple users,
or DELETE
multiple swings. To accomplish this, rather than specifying a single UUID
like we would when fetching a
specific resource, we need to specify a list of comma separated UUID
values.
- Requesting multiple users:
GET /users/:uuids
- Requesting batting sessions for multiple users:
GET /users/:uuids/battingSessions
- Deleting multiple swings:
DELETE /swings/:uuids
- Increase character count for request URL in tomcat https://serverfault.com/questions/56691/whats-the-maximum-url-length-in-tomcat
- Partial Success? Or only success/failure?
We declare the major version of the API to use in the URL
- User profile version 2:
GET /v2/users/:uuid/profile
- Batting sessions version 3:
GET /v3/battingSessions
- Documentation
- Better Swagger front-end
- Version fallback for unimplemented next version URLs
- For discussion: What happens when we fallback to a version with DRASTICALLY different formats?
- Date-based sub-versions that can be specified using custom HTTP request headers.
An API should provide a useful error message in a known consumable format such as JSON. The error body should provide at least the HTTP status code, along with a message. A more detailed description of the error can be included as needed.
- Conflict, resource already exists.
{
"code": 409,
"message": "That thing already exists",
"description": "You can't make this, it's already a thing."
}
- Field specific validation errors should be included for
PUT
,PATCH
, andPOST
requests. This should be modeled using a fixed top-level error code for validation failures and providing the detailed errors in an additional errors field.
{
"code": 400,
"message": "Validation failed",
"errors": [
{
"code": 123,
"field": "firstName",
"message": "First name is required"
},
{
"code": 124,
"field": "nickname",
"message": "Nickname cannot contain spaces"
}
]
}
Pretty print responses with the extra whitespace. Removing whitespace does not save much bandwidth, usually around 8.5%. With GZip compression enabled, removing all whitespace only saves around 2.5%. GZipping in itself saves around 60% in bandwidth savings. Pretty printing with GZip enabled is preferred.