Skip to content

Commit fef4411

Browse files
authored
Merge pull request #964 from runspired/data/deprecate-legacy-finder-support
EmberData | deprecate legacy finder support
2 parents 7d2ff96 + f5e6a56 commit fef4411

File tree

1 file changed

+300
-0
lines changed

1 file changed

+300
-0
lines changed
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
---
2+
stage: accepted
3+
start-date: 2023-09-18T00:00:00.000Z
4+
release-date: # In format YYYY-MM-DDT00:00:00.000Z
5+
release-versions:
6+
teams: # delete teams that aren't relevant
7+
- data
8+
prs:
9+
accepted: https://github.com/emberjs/rfcs/pull/964
10+
project-link:
11+
suite:
12+
---
13+
14+
<!---
15+
Directions for above:
16+
17+
stage: Leave as is
18+
start-date: Fill in with today's date, 2032-12-01T00:00:00.000Z
19+
release-date: Leave as is
20+
release-versions: Leave as is
21+
teams: Include only the [team(s)](README.md#relevant-teams) for which this RFC applies
22+
prs:
23+
accepted: Fill this in with the URL for the Proposal RFC PR
24+
project-link: Leave as is
25+
suite: Leave as is
26+
-->
27+
28+
# EmberData | Deprecate Legacy Request Support
29+
30+
## Summary
31+
32+
Deprecates methods on `store` and `model` that utilize non-request-manager request paradigms. These
33+
methods are no longer recommended in the face of the greater utility of `store.request`.
34+
35+
Deprecates methods on store associated to older request paradigms / the inflexibility of older paradigms.
36+
37+
These deprecations would target 6.0.
38+
39+
## Motivation
40+
41+
This RFC is a debt collection. The newer RequestManager paradigm offers a pipeline approach to requests and preserves the context of the request throughout its lifecycle. This newer paradigm solves the issues
42+
with limited power and flexibility that the adapter/serializer approach suffered and which led to so many ejects to fetch+push, work arounds, frustrations, and library removals.
43+
44+
The RequestManager paradigm ensures that any request starts by providing FetchOptions. These may be built programmatically (for instance for relationships or collections that return links information from the API), or by builders, or even manually.
45+
46+
Users have the choice of providing the body for a request at the point of the request, or inserting one later using a request handler. For both cases, EmberData provides access to the cache and its APIs for diffing state changes, as well as `serializePatch` and `serializeResources` utils for each cache implementation that we ship.
47+
48+
The paradigm has the simple goal of "use the platform". Importantly, it is this usage that allows us to intelligently cache requests and documents alongside resources, opening up vast possibilities for helpful new features for EmberData that these older methods do not support.
49+
50+
The legacy pattern's inflexibility meant that users often needed to eject from using adapter/serializer paradigms to fetch data. The new paradigm does not have these constraints, so we wish to deprecate methods that served only as work arounds or only work with these now legacy concepts.
51+
52+
## Detailed design
53+
54+
### Deprecating Historical Request Methods
55+
56+
Currently, the Store allows users to continue using the historical methods for fetching or mutating data.
57+
58+
**Group 1**
59+
60+
- `store.findRecord`
61+
- `store.findAll`
62+
- `store.query`
63+
- `store.queryRecord`
64+
- `store.saveRecord`
65+
- `model.save`
66+
- `model.reload`
67+
- `model.destroyRecord`
68+
69+
These historical methods include the `hasMany` and `belongsTo` async auto-fetch behaviors on `@ember-data/model`
70+
71+
**Group 2**
72+
- accessing an async belongsTo
73+
- accessing an async hasMany
74+
75+
As well as corresponding relationship reference fetching methods
76+
77+
**Group 3**
78+
- `HasManyReference.load()`
79+
- `HasManyReference.reload()`
80+
- `BelongsToReference.load()`
81+
- `BelongsToReference.reload()`
82+
83+
These historical request methods currently translate their arguments into the shape expected by RequestManager with a few major caveats:
84+
85+
- they require using the LegacyCompatHandler and adapter/serializer logic or something mimicking it
86+
- they are not as flexible at the callsite
87+
- they are not cacheable since they do not set cacheKey and do not provide a url
88+
89+
Now that builders have shipped in 5.3, deprecating all of group 1 allows us to begin simplifying the mental model of how EmberData should be used.
90+
91+
Groups 2 and 3 should not be deprecated until we've either provided an alternative decorator to replace async `belongsTo` and `hasMany` or shipped [SchemaRecord](#8845)
92+
93+
#### What to do instead
94+
95+
Examples here are shown for apps that use `JSON:API`. Apps using other paradigms should use the builders for `REST` or `ActiveRecord` if
96+
applicable, or author their own (or a new community lib!) if not.
97+
98+
- `store.findRecord`
99+
- `model.reload`
100+
101+
```ts
102+
import { findRecord } from '@ember-data/json-api/request';
103+
104+
const result = await store.request(findRecord('user', '1'));
105+
const user = result.content.data;
106+
```
107+
108+
- `store.findAll`
109+
110+
If you don't want all records in the store + what the API says
111+
112+
```ts
113+
import { query } from '@ember-data/json-api/request';
114+
115+
const result = await store.request(query('user'));
116+
const users = result.content.data;
117+
```
118+
119+
If you do want all records in the store + what the API says. Note,
120+
we would heavily discourage this approach having watched as it leads
121+
to difficult to disentangle complexity in applications.
122+
123+
```ts
124+
import { query } from '@ember-data/json-api/request';
125+
126+
await store.request(query('user'));
127+
const users = store.peekAll('user');
128+
```
129+
130+
- `store.query`
131+
132+
For requests that are expected to send a "body" to the API see notes
133+
in the saveRecord section below.
134+
135+
```ts
136+
import { query } from '@ember-data/json-api/request';
137+
138+
const result = await store.request(query('user', params));
139+
const users = result.content.data;
140+
```
141+
142+
- `store.queryRecord`
143+
144+
```ts
145+
import { query } from '@ember-data/json-api/request';
146+
147+
const result = await store.request(query('user', { ...params, limit: 1 } ));
148+
const user = result.content.data[0] ?? null;
149+
```
150+
151+
- `store.saveRecord`
152+
- `model.save`
153+
154+
For requests that are expected to send a "body" to the API applications
155+
may choose to either serialize the body at the point of the request or
156+
to implement a Handler for the RequestManager to do so.
157+
158+
EmberData does not provide a default handler which serializes because this
159+
is a unique concern for every app. However, EmberData does provide utilities
160+
on both the Cache and for some of the builders to make this easy.
161+
162+
For JSON:API we show the "at point of request" approach using the utils
163+
provided by the `@ember-data/json-api` package here.
164+
165+
**for create**
166+
167+
```ts
168+
import { recordIdentifierFor } from '@ember-data/store';
169+
import { createRecord, serializeResources } from '@ember-data/json-api/request';
170+
171+
const record = store.createRecord('user', {});
172+
const request = createRecord(record);
173+
request.body = JSON.stringify(
174+
serializeResources(
175+
store.cache,
176+
recordIdentifierFor(record)
177+
)
178+
);
179+
180+
await store.request(request);
181+
```
182+
183+
**For update**
184+
185+
```ts
186+
import { recordIdentifierFor } from '@ember-data/store';
187+
import { updateRecord, serializePatch } from '@ember-data/json-api/request';
188+
189+
user.name = 'Chris';
190+
191+
const request = updateRecord(user);
192+
request.body = JSON.stringify(
193+
serializePatch(
194+
store.cache,
195+
recordIdentifierFor(user)
196+
)
197+
);
198+
199+
await store.request(request);
200+
```
201+
202+
Note if you only wanted to save the single mutation you just made, you could.
203+
204+
```ts
205+
import { updateRecord, serializePatch } from '@ember-data/json-api/request';
206+
207+
// local mutation (reflected on model immediately)
208+
user.name = 'Chris';
209+
210+
const request = updateRecord(user);
211+
request.body = JSON.stringify(
212+
{
213+
data: {
214+
type: 'user',
215+
id: user.id,
216+
attributes: {
217+
name: user.name
218+
}
219+
}
220+
}
221+
);
222+
223+
await store.request(request);
224+
```
225+
226+
**for delete**
227+
228+
- also `model.destroyRecord`
229+
230+
```ts
231+
import { deleteRecord } from '@ember-data/json-api/request';
232+
233+
store.deleteRecord(user);
234+
await store.request(deleteRecord(user));
235+
store.unloadRecord(user);
236+
```
237+
238+
### For Async Relationships / Sync Relationship Reload
239+
240+
Use [LinksMode](https://github.com/emberjs/data/blob/main/guides/relationships/features/links-mode.md) which enables autofetch / explicit fetch of a relationship to utilize the RequestManager pipeline in
241+
full instead of adapters/serializers via the LegacyNetworkHandler.
242+
243+
### Deprecating Store Data Munging
244+
245+
Additionally, we deprecate store methods for data munging:
246+
247+
- `store.pushPayload`
248+
- `store.serializeRecord`
249+
250+
#### What to do instead
251+
252+
**For Modern Apps**
253+
254+
- align the cache and API format, use `store.push` to upsert
255+
- use the same normalization functions written for handling responses in the app's request handlers, use `store.push` to upsert
256+
- migrate the request to just use `RequestManager` now that the limitations of the adapter pattern are gone
257+
258+
**For Apps still using Legacy**
259+
260+
- Use `store.serializerFor` and `serializer.normalizeResponse` to normalize payloads before using `store.push`.
261+
- Some previous discussion https://github.com/emberjs/data/issues/4213#issuecomment-413370235
262+
- Some examples of how to work without this method: https://github.com/emberjs/data/pull/4110#issuecomment-417391930
263+
264+
265+
## How we teach this
266+
267+
- API Docs should remove usage examples of older patterns, replacing them with
268+
newer patterns. Ensure consistency before deprecating.
269+
- Guides, and the Tutorial should remove usage examples of older patterns, replacing them with
270+
newer patterns.
271+
- We do not activate this deprecation until at least the tutorial has been updated to new patterns
272+
273+
## Drawbacks
274+
275+
The only drawback here is that this deprecation doesn't go further. We do not at this time deprecate adapters
276+
and serializers or the LegacyNetworkHandler. This is because to do so we must also deprecate the auto-fetch
277+
behaviors of async relationships in `@ember-data/model`. We prefer to deprecate those aspects of the system
278+
only once replacements are firmly in place.
279+
280+
However, we think continuing to clarify the mental model for everything else is important, especially because
281+
`@ember-data/model` is not a required component of an EmberData installation and users can utilize everything
282+
else today without Adapter/Serializer/Model should they so choose.
283+
284+
## Alternatives
285+
286+
Convert all of these APIs to expect builder input. We have not taken this avenue as we feel the scope of that
287+
deprecation would be much harder to manage and much tougher to navigate. However, an application may choose to
288+
extend the store and implement any of the request method they choose in this manner because they have the knowledge
289+
of which builder to use.
290+
291+
```ts
292+
import { findRecord } from 'app/builders';
293+
294+
class extends Store {
295+
async findRecord(type, id, options?): Promise<Record | null>; {
296+
const result = await this.request(findRecord(type, id, options));
297+
return result.content.data;
298+
}
299+
}
300+
```

0 commit comments

Comments
 (0)