-
Notifications
You must be signed in to change notification settings - Fork 0
docs: #3 architecture decision records #36
Merged
+106
−0
Merged
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
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,103 @@ | ||
| # Architecture decisions | ||
|
|
||
| The main technical choices for Crate and why we made them. Most are implemented | ||
| in M2 and M3. The API contract is `schema.graphql`, and the data model is in | ||
| `docs/data-model.md`. | ||
|
|
||
| ## 1. PostgreSQL + GraphQL Yoga | ||
|
|
||
| **Context:** The data is relational. Albums have many artists and genres, and | ||
| reviews and crate items link a user to an album. Search needs counts, genre | ||
| facets and average ratings. Everything runs on the group VM. | ||
|
|
||
| **Options:** PostgreSQL, MongoDB, Neo4j, or a backend-as-a-service such as | ||
| Firebase. | ||
|
|
||
| **Decision:** PostgreSQL with a GraphQL Yoga server. MongoDB would need | ||
| duplicated data or manual joins. Neo4j is built for graph traversal, and our | ||
| queries are search, filter, sort and page. A BaaS would hide the server-side | ||
| parts this project is about (decisions 2 to 4) and keep the data off our VM. | ||
|
|
||
| **Consequences:** Joins, aggregates, constraints (one review per user per album) | ||
| and full-text search come built in. In return we write migrations and run the | ||
| database ourselves. | ||
|
|
||
| ## 2. Keyset pagination | ||
|
|
||
| **Context:** The data set has no fixed size, so paging must stay fast on any | ||
| page. | ||
|
|
||
| **Options:** `LIMIT`/`OFFSET`, or keyset pagination. | ||
|
|
||
| **Decision:** Keyset. The cursor stores the last row's sort value and id, and | ||
| the next page continues from there (`first` + `after` in the schema). `OFFSET` | ||
| reads and throws away every skipped row, so it gets slower on later pages, and | ||
| it can repeat or skip rows when data changes between requests. | ||
|
|
||
| **Consequences:** Every page is equally fast. Paging only goes forward, so there | ||
| is no "jump to page N". Each sort mode needs its own cursor logic, including | ||
| nullable fields such as release year. | ||
|
|
||
| ## 3. Sorting and filtering in SQL | ||
|
|
||
| **Context:** Sorting and filtering must apply to the whole result set, not just | ||
| the loaded page. `totalCount` and genre facets also need every match. | ||
|
|
||
| **Options:** In SQL, in the resolver, or in the client. | ||
|
|
||
| **Decision:** In SQL, as one parameterised query built from the search term, | ||
| genres and sort. Sorting a loaded page in the client gives the wrong answer, and | ||
| sorting in the resolver loads every match into memory. | ||
|
|
||
| **Consequences:** Results and counts are correct and can use indexes. The query | ||
| builder is the most complex server code, so it needs tests against a real | ||
| database. | ||
|
|
||
| ## 4. DataLoaders against N+1 | ||
|
|
||
| **Context:** GraphQL resolves each field on its own. A page of 20 albums asking | ||
| for artists, genres and ratings would otherwise run a few queries per album. | ||
|
|
||
| **Options:** Naive field resolvers, one large join up front, or DataLoaders. | ||
|
|
||
| **Decision:** Every relation field goes through a DataLoader created per | ||
| request. The loader collects all album ids in the request and runs one query for | ||
| them. | ||
|
|
||
| **Consequences:** A page costs a fixed number of queries whatever its size. | ||
| Because the loaders are per request, one user's data (`myReview`, `isInCrate`) | ||
| is never cached for another. | ||
|
|
||
| ## 5. Simulated users instead of login | ||
|
|
||
| **Context:** Reviews and crates must persist on the server. The course allows | ||
| simulated users. | ||
|
|
||
| **Options:** Real login, third-party login, or a simulated user. | ||
|
|
||
| **Decision:** The client generates a UUID, keeps it in `localStorage` and sends | ||
| it with each request. The server reads it, so the `my...` fields in the schema | ||
| need no user argument. | ||
|
|
||
| **Consequences:** Much less to build. There is no security, since anyone can | ||
| send another user's id, and clearing storage gives a new user. | ||
|
|
||
| ## 6. Plain CSS, Apollo Client cache, URL as search state | ||
|
|
||
| **Context:** A small client that needs good accessibility, a small bundle, and | ||
| searches that survive reload and can be shared. | ||
|
|
||
| **Options:** A CSS framework or component library, or plain CSS. A separate | ||
| state store such as Redux, or the Apollo cache. Search state in components, or | ||
| in the URL. | ||
|
|
||
| **Decision:** | ||
|
|
||
| - **Plain CSS** for a smaller bundle and full control over focus and contrast. | ||
| - **The Apollo cache** as the store for server data. Mutations return the | ||
| updated album, so every view of that album updates without a refetch. | ||
rachelks marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| - **The URL** holds the search term, genres and sort. | ||
|
|
||
| **Consequences:** We write all the styling ourselves. There is one source of | ||
| truth for server data. Reload, back/forward and shared links reproduce the same | ||
| search. | ||
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.