Skip to content

Commit de00380

Browse files
committed
Moved Backend into API Server
1 parent 88798ff commit de00380

File tree

71 files changed

+2846
-24
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+2846
-24
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
title: REST API Basics
3+
sidebar_order: 1
4+
---
5+
This section includes common terms and resources to learn more about API design. If you're new to API design, this is a good place to start.
6+
7+
## Common Terms
8+
- **Resource** is the object you’re performing the action on with your endpoint. For example, in ProjectEndpoint the resource is Project.
9+
- **Resource Identifier** can be an ID, like an event ID, or slug, like an organization slug. Note that it must be unique. For example, Sentry's project resource identifier is \{org_slug}/\{project_slug}, because projects are only unique within their organization. You can find more information about this in the slug vs. ID section.
10+
- **Method** is what you do with the resource. Each endpoint can have multiple methods. For example in ProjectEndpoint, you can have a GET method that returns details of a specific project.
11+
- **Collection** is basically an object type. You can map them to a Django object type like an Organization or a text object like an error.
12+
13+
## Extra Resources
14+
The following resources are helpful to learn about general API design best practices:
15+
16+
- **[The Design of Web APIs](https://g.co/kgs/R7rXEk)** is an example-packed guide to designing APIs.
17+
- **[API Design Patterns](https://g.co/kgs/Vfnpe5)** is comprehensive guide on key API patterns.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
title: Public API Checklist
3+
sidebar_order: 5
4+
---
5+
6+
Below is a checklist that developers should go through before making APIs public.
7+
8+
1. APIs must return JSON. Exceptions to this rule are APIs where the user is specifically requesting for non JSON data. For example, requesting a CSV file or a debug file.
9+
1. The API should be available if requested with a bearer token from the integration platform.
10+
1. There are scopes that can be selected in the integration platform that enables or disables access to the resource exposed by the API.
11+
1. If the API call being made is resource intensive on Sentry's infrastructure, there is a rate limit in place which rate limits excessive calls.
12+
1. APIs returning a variable number of elements as a list should be paginated. They should be paginated using the link header standard.
13+
1. End user requests should not easily be able to trigger 5xx errors in our application by manipulating request data or sending invalid/malformed data.
14+
1. There are tests associated with the API which look for correctness. There should also be tests that check the response format to ensure the API does not deviate from its contract.
15+
1. The API should return the same format for a status code, irrespective of the input provided. The content can be different but the response format should be the same.
16+
1. When an API is only available when certain feature flags are enabled (perhaps through a pricing plan), the API should check for that. If a bearer token of an organization who does not have access to the feature is used, the API should return with a 403 Forbidden and an error explaining which feature flag is required.
17+
1. APIs should use camelCase for query parameters and request/response bodies.
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
---
2+
title: API Concepts
3+
sidebar_order: 3
4+
---
5+
In this document, we will be looking at API concepts that exist and should be followed by endpoints. We also describe why these concepts exist so that developers can use them at their own discretion.
6+
7+
## Expanding responses
8+
Expanding responses allow us to include relational information on a resource without loading it by default.
9+
10+
In general, endpoints should expose the fewest fields that will make the API usable in the general scenario. Doing one SQL request per API request is a good rule of thumb. To return information on a bounded relationship, endpoints should rely on the `expand` parameter. To return an unbounded relationship, it should be another endpoint.
11+
12+
To take an example, let's talk about the projects list endpoint. A project belongs to an organizations but could be on multiple teams.
13+
14+
By default, here's what the project endpoint should look like
15+
16+
```json
17+
GET /api/0/projects/{project_slug}/
18+
{
19+
"id": 5,
20+
"name": "foo",
21+
...
22+
}
23+
```
24+
25+
To display information about a bounded relationship, a user should be able to use the `expand` parameter. This is generally only true for 1:1 relationships.
26+
27+
```json
28+
GET /api/0/projects/{project_slug}/?expand=organization
29+
{
30+
"id": 5,
31+
"name": "foo",
32+
"organization": {
33+
"slug": "bar",
34+
"isEarlyAdopter": false,
35+
...
36+
}
37+
...
38+
}
39+
```
40+
41+
For unbounded relationships, make a separate query. This allows the query to be paginated and reduces the risk of having an arbitrarily large payload.
42+
43+
```json
44+
GET /api/0/projects/{project_slug}/teams
45+
[
46+
{
47+
"id": 1,
48+
"name": "Team 1",
49+
"slug": "team1",
50+
},
51+
{
52+
"id": 2,
53+
"name": "Team 2",
54+
"slug": "team2",
55+
}
56+
]
57+
58+
```
59+
60+
## Collapsing responses
61+
Similar to expanding responses, an API endpoint can also collapse responses. When the `collapse` parameter is passed, the API should not return attributes that have been collapsed.
62+
63+
To take an example, let's look at the project list endpoints again. A project gets events and hence, has a `stats` component, which conveys information about how many events were received for the project. Let's say we made the stats part of the endpoint public, along with the rest of the projects list endpoint.
64+
65+
```json
66+
GET /api/0/projects/{project_slug}/
67+
{
68+
"id": 5,
69+
"name": "foo",
70+
"stats": {
71+
"24h": [
72+
[
73+
1629064800,
74+
27
75+
],
76+
[
77+
1629068400,
78+
24
79+
],
80+
...
81+
]
82+
}
83+
}
84+
```
85+
86+
The `collapse` parameter can be passed to not return stats information.
87+
88+
```json
89+
GET /api/0/projects/{project_slug}/?collapse=stats
90+
{
91+
"id": 5,
92+
"name": "foo",
93+
...
94+
}
95+
```
96+
97+
This is typically only needed if the endpoint is already public and we do not want to introduce a breaking change. Remember, if the endpoint is public and we remove an attribute, it is a breaking change. If you are iterating on an undocumented endpoint, return the minimal set of attributes and rely on the `expand` parameter to get more detailed information.
98+
99+
## Paginating responses
100+
>> APIs often need to provide collections of data, most commonly in the `List` standard method. However, collections can be arbitrarily sized, and tend to grow over time, increasing lookup time as well as the size of the responses being sent over the wire. This is why it's important for collections to be paginated.
101+
102+
Paginating responses is a [standard practice for APIs](https://google.aip.dev/158), which Sentry follows.
103+
We've seen an example of a `List` endpoint above; these endpoints have two tell-tale signs:
104+
```json
105+
GET /api/0/projects/{project_slug}/teams
106+
[
107+
{
108+
"id": 1,
109+
"name": "Team 1",
110+
"slug": "team1",
111+
},
112+
{
113+
"id": 2,
114+
"name": "Team 2",
115+
"slug": "team2",
116+
}
117+
]
118+
119+
```
120+
1. The endpoint returns an array, or multiple, objects instead of just one.
121+
2. The endpoint can sometimes end in a plural (s), but more importantly, it does __not__ end in an identifier (`*_slug`, or `*_id`).
122+
123+
To paginate a response at Sentry, you can leverage the [`self.paginate`](https://github.com/getsentry/sentry/blob/24.2.0/src/sentry/api/base.py#L463-L476) method as part of your endpoint.
124+
`self.paginate` is the standardized way we paginate at Sentry, and it helps us with unification of logging and monitoring.
125+
You can find multiple [examples of this](https://github.com/getsentry/sentry/blob/24.2.0/src/sentry/api/endpoints/api_applications.py#L22-L33) in the code base. They'll look something like:
126+
```python
127+
def get(self, request: Request) -> Response:
128+
queryset = ApiApplication.objects.filter(
129+
owner_id=request.user.id, status=ApiApplicationStatus.active
130+
)
131+
132+
return self.paginate(
133+
request=request,
134+
queryset=queryset,
135+
order_by="name",
136+
paginator_cls=OffsetPaginator,
137+
on_results=lambda x: serialize(x, request.user),
138+
)
139+
```
140+
141+
The example above uses an offset type paginator, but feel free to use whatever paginator type suits your endpoint needs.
142+
There are some [existing types](https://github.com/getsentry/sentry/blob/24.2.0/src/sentry/api/paginator.py) that you can leverage out of the box, or you can extend the base class [`BasePaginator`](https://github.com/getsentry/sentry/blob/24.2.0/src/sentry/api/paginator.py#L48) and implement your own.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
---
2+
title: Designing a New API
3+
sidebar_order: 2
4+
---
5+
6+
[Django REST framework](https://www.django-rest-framework.org/)(DRF) is a powerful and flexible toolkit
7+
for building Web APIs. Sentry APIs are built using DRF. Here are some considerations to make when designing APIs at Sentry.
8+
9+
## URL Patterns
10+
The API's URL is what developers use to call the endpoint so it’s important that it is meaningful and clear.
11+
12+
### Don't Exceed 3-level Nesting
13+
Nested resources format like `api/0/organizations/\{org}/projects/` is recommended over `api/0/projects/` for readability because it gives user an understanding of resource hierarchy. However, nesting can make URLs too long and hard to use. Sentry uses 3-level nesting as a hybrid solution.
14+
15+
Here are some possible urls for values with this resource hierarchy: organization -> project -> tag -> value
16+
- 👍 `/projects/\{organization_slug}/\{project_slug}/tags/\{tag_id}/values`
17+
- 👎 `/organizations/\{organization_slug}/projects/\{project_slug}/tags/\{tag_id}/values/`
18+
- 👎 `/values/`
19+
20+
In the above example we flattened `projects`. The table below shows the existing flattened collections which works out with our existing APIs.
21+
22+
| First collection in URL | When to use | Parent | Identifier | Example |
23+
| --- | --------- | ----- | --- | --- |
24+
| organizations | When the resource cannot be attached to any other collection below parent like Project | N/A - always comes as first collection | \{organization_slug} | [Create a New Team](https://docs.sentry.io/api/teams/create-a-new-team/) |
25+
| teams | When the resource is under a specific team in the hierarchy | organizations | \{organization_slug}/ \{team_slug} | [Retreive Team Stats](https://docs.sentry.io/api/teams/retrieve-event-counts-for-a-team/) |
26+
| projects | When the resource is under a specific project in the hierarchy but not under an issue | organizations | \{organization_slug}/ \{project_slug} | [Create a New Client Key](https://docs.sentry.io/api/projects/create-a-new-client-key/)|
27+
| issues | When the resource is under a specific issue in the hierarchy | projects | \{issue_id} | [List an Issue's Events](https://docs.sentry.io/api/events/list-an-issues-events/)|
28+
| sentry-app-installations | When the resource is mapped to a specific integration | organizations | \{integration_slug} | [Delete an External Issue](https://docs.sentry.io/api/integration/delete-an-external-issue/)|
29+
30+
Here are some additional examples:
31+
- 👍 `/organizations/\{organization_slug}/projects/`
32+
- 👍 `/projects/\{organization_slug}/\{project_slug}/issues/`
33+
- 👎 `/projects/`
34+
35+
<Alert title="Note" level="info">
36+
37+
Hierarchy here does not necessarily mean that one collection belongs to a parent collection. For example:
38+
- `projects/\{project_identifier}/teams/` refers to the **teams** that have been added to specific project
39+
- `teams/\{team_identifier}/projects/` refers to the **projects** a specific team has been added to
40+
41+
</Alert>
42+
43+
### Naming Guidance
44+
- Collection names should be lowercase and hyphenated, e.g. `commit-files`.
45+
- Collection names must be plural. Avoid using uncountable words because the user can’t know whether the GET returns one item or a list.
46+
- Query params and body params should be `camelBacked`. eg. `userId` or `dateCreated`.
47+
- For sorting and filtering, stick with the common param names: `sortBy` (e.g. `sortBy=-dateCreated`), `orderBy` (either `asc` or `desc`), `groupBy`, `limit`
48+
- Path params should be `snake_case`
49+
50+
## Functionality
51+
Each API should be **stateless**, have a clear purpose, and do one specific thing. To achieve that, stick with the standard methods listed below. If your API needs to be more complicated, work with [owners-api](https://github.com/orgs/getsentry/teams/owners-api) on how to create it.
52+
53+
- 👍 An API that updates project settings: PATCH for updating a field or PUT for updating settings
54+
- 👎 An API that creates a project, creates a team, and creates alerts for that team about that project
55+
56+
| Functionality | HTTP Method | Response Object | Example |
57+
| --- | ---- | ---- | ---- |
58+
| Create | POST | Serialized created resource | [Create a Project](https://github.com/getsentry/sentry/blob/756bda4419cfaf28b2e351278a5c4c1665082eba/src/sentry/api/endpoints/team_projects.py#L156) |
59+
| Update | PUT or PATCH | Serialized updated resource | [Update Project Settings](https://github.com/getsentry/sentry/blob/756bda4419cfaf28b2e351278a5c4c1665082eba/src/sentry/api/endpoints/project_details.py#L474) |
60+
| Get | GET | Serialized single resource | [Retrieve a Project](https://github.com/getsentry/sentry/blob/756bda4419cfaf28b2e351278a5c4c1665082eba/src/sentry/api/endpoints/project_details.py#L415) |
61+
| Delete | DELETE | None | [Delete a Project](https://github.com/getsentry/sentry/blob/756bda4419cfaf28b2e351278a5c4c1665082eba/src/sentry/api/endpoints/project_details.py#L840) |
62+
| List | GET | List of multiple serialized resources | [List All the Projects in an Organization](https://github.com/getsentry/sentry/blob/756bda4419cfaf28b2e351278a5c4c1665082eba/src/sentry/api/endpoints/organization_projects.py#L49) |
63+
| Batch Get | GET | List of serialized resources | Get project details for specific project ids |
64+
| Batch Create | POST | List of serialized created resources | Create multiple projects with the same settings |
65+
| Batch Update | PUT | List of serialized updated resources | [Update a list of issues](https://github.com/getsentry/sentry/blob/ea14f740c78b8df68281ffad6a3bf3709ed3d5b5/src/sentry/api/endpoints/organization_group_index.py#L379) |
66+
| Batch Delete | DELETE | None | [Delete a list of issues](https://github.com/getsentry/sentry/blob/ea14f740c78b8df68281ffad6a3bf3709ed3d5b5/src/sentry/api/endpoints/organization_group_index.py#L467)|
67+
68+
Here are some examples of how to use standard methods to represent complex tasks:
69+
- Get count of a resource
70+
- Count is part of the `List` API and is provided in header X-Total-Count param
71+
- Get latest of a resource
72+
- Order and filtering should happen as part of list api query parameters. Here’s a [good read](https://www.moesif.com/blog/technical/api-design/REST-API-Design-Filtering-Sorting-and-Pagination/).
73+
- 👎 `/api/0/issues/\{issue_id}/events/latest/`
74+
- 👍 `/api/0/issues/\{issue_id}/events?orderBy=-date,limit=1`, `-` for descending
75+
76+
### Batch vs. Single Resource Methods
77+
Here are some notes that can help you decide between similar methods. We use *Get* here as an example but the same applies to all the other methods in the parenthesis.
78+
- **Get (Update, Delete)**: Use get on the `\{resource}DetailsEndpoint` to retrieve a resource. For example, `ProjectDetailsEndpoint`.
79+
- **List (Create, Batch Create, Batch Update, Batch Delete)**: Use get on the `\{resource-parent}\{resource}Endpoint` to retreive all resources that belong to that parent. For example `TeamProjectsEndpoint`.
80+
- **Batch Get (Batch Create, Batch Update, Batch Delete)**: Use get on the `\{root-parent}\{resource}Endpoint`. The difference between `Batch` and `List` is that batch usually includes a list of `ids` as query parameter and returns details about those ids. This list does not necessarily belong to one parent. For example, we can't retrieve two projects that belong to two different teams in the above example and in that case we use the get method in the root resource, in this case `OrganizationProjectsEndpoint`.
81+
82+
## Response Object
83+
Each response object returned from an API should be a serialized version of the Django model associated with the resource. You can see all the existing serializers [here](https://github.com/getsentry/sentry/tree/master/src/sentry/api/serializers/models).
84+
85+
<Alert title="Note" level="info">
86+
87+
Some models might have different serializers based on use case. For example, `Project` can be serialized into `DetailedProjectSerializer` or `ProjectSerializer`. Decide which one to use based on your use case and API scope but **DO NOT RETURN CUSTOM OBJECTS** like \{`slug: project_slug, platform: project_platform`}. We want the API responses to be uniform and useable in multiple automations without adding extra complication to the external developers' code.
88+
89+
90+
</Alert>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
title: API Development
3+
sidebar_order: 20
4+
---
5+
6+
As a developer-facing company it's critical for us to have simple, intuitive, and consistent APIs that our users can call from their dev environment to accomplish key tasks without going to the UI. If you're creating or editing an endpoint, this doc will help you achieve Sentry standards.
7+
8+
<PageGrid />

0 commit comments

Comments
 (0)