Understanding the new Request Manager & Handler in Ember 4.12

I’m struggling to understand as how my previous implementation (adapters & serializers) able to adopt the new Request Manager, Store & Handlers as in 4.12. By the looks of it, this is also how Ember planning to carry forward - as current documentation (for Adapters & Serializers) shows deprecation warning asking to adopt the new architecture instead.

I always a fan of EmberData and the use of Store methods (findAll, createRecord etc).

Previously, when making requests, I’m able to use REST adapters and Embedded Record serialisers.

The structure also makes sense, where adapters are in one folder while multiple serializers for multiple models are in one folder.

The flow or hierarchy of data also makes sense in “old” architcture.

I’ve been playing around with the new Request Manager and Handler - while the guide is limited and not plenty tutorials available. Here is an example:

/models/todo.js

import Model, { attr, hasMany, belongsTo } from '@ember-data/model';

export default class TodoModel extends Model {
  @attr('string') title;
  @attr('boolean') completed;
  @belongsTo('user') userId;
}

/services/request.js Using dummy data fetch URL

import RequestManager from '@ember-data/request';
import { CacheHandler } from '@ember-data/store';
import { service } from '@ember/service';

// Custom handler to show how to use a custom fetch implementation.
const RESTHandler = {
    async request(context) {
        // Build the request URL following Request API.
        const response = await fetch('https://jsonplaceholder.typicode.com/todos/', { 
            method: 'GET'
        });
        context.setResponse(response);
        context.setStream(response.clone().body);
        return response.json();
    },
};

export default class extends RequestManager {
    constructor(createArgs) {
        super(createArgs);

        // Specify which handler to use to fullfill requests.
        this.use([RESTHandler]);

        // Specify which cache handler to use to cache responses.
        this.useCache(CacheHandler);
    }
}

/services/store.js

import Store from '@ember-data/store';
import { service } from '@ember/service';
import Cache from '@ember-data/json-api';

export default class extends Store {
    @service('request') requestManager;
    
    createCache(storeWrapper) {
        // When is the called? Where do I create my own REST wrapper??
        return new Cache(storeWrapper);
    }

    instantiateRecord(identifier) {
        // When is this also called?
        console.log(identifier)
    }
}

To get data, I use the standard method in /routes/todos.js

import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';

export default class TodosRoute extends Route {
    @service store;

    model() {
        return this.store.findAll('todo');
    }
}

The request was successful and returned response like so:

[
    {
        "userId": 1,
        "id": 1,
        "title": "delectus aut autem",
        "completed": false
    },
    {
        "userId": 1,
        "id": 2,
        "title": "quis ut nam facilis et officia qui",
        "completed": false
    },
    {
        "userId": 1,
        "id": 3,
        "title": "fugiat veniam minus",
        "completed": false
    }
]

Simple template like this, rendered successfully - using raw response eventhough not in JSON:API spec as Ember usually expects by default.

{{page-title "Todos"}}
<h1>To-do List</h1>
<ol>
  {{#each @model as |todo|}}
    <li>{{todo.title}}</li>
  {{/each}}
</ol>

However, Ember Inspector shows Data blank with no model shown. Do I have to push payload to Store? Normalize? Where do I do this?

When creating record like so;

this.store.createRecord('todo', {
   title: 'add a new task to the list',
   completed: false,
})

Ember Inspector Data shows no record. When calling save(), returns error and no network request made.

Couple of questions

  1. How adapter has been replaced? I assume ‘Handlers’.
  2. How serializers has been replaced? I assume ‘Cache’.

My case would probably fit this diagram from @ember-data/request:

However, in set up above no Handlers involved?

I would greatly appreciate if someone can help me get my head around this. I will be sharing my ‘before-and-after’ with the hope to help others too.

Sy

2 Likes

No, actually. Both are replace by handlers, if replaced at all. One of the things people misunderstood about adapters and serializers is that they were never intended to be permanent solutions for any app, you were supposed to align your API to the cache format over time, and the adapter and serializer were a way to massage it a bit if you hadn’t.

Going forward, if you need to do this massaging, use a handler. But the advice is still the same: align your cache and API. Unlike the past though, it is now possible to make the cache match your API format vs only having the option of changing the API to match cache. To do this you would have to write a cache implementation which you then provide as the return value of the the store’s createCache method.

The RequestManager on its own does nothing without a handler. It is the thing the coordinates ensuring handlers are properly invoked, but does zero building or fulfilling of requests on its own.

I suspect you misunderstand the old architecture significantly and build simple enough things at small enough scale that its myriad issues never stood-out. That diagram hides a ton of problems: none the least of which are that it leads to an untrustworthy cache and that adapters and serializers had zero context about when, why or how they were being asked to provide data. Is it nice they are in some folders? possibly, but more people bailed on EmberData due to this than stuck with it. You can’t design a library only for those with surviver bias.

If you didn’t normalize and your API wasn’t in the cache format then yes, you need to normalize (or as mentioned above change your API or change out the cache). The reason the store acts like there are no records after your request is because indeed there are no records, there was nothing it could identify as a resource in the response.

I dislike that anyone considers findAll a standard method, though I understand why they do. This is why findAll will have no replacement going forward. It is a dangerous architectural decision that only leads teams into the depths of difficult to fix application architecture and horrendous performance, usability, and accessibility issues. I highly recommend using request to fetch a specific page of results. When used properly for collections it returns a document that can be treated as an iterator across pages.

2 Likes