hyoo-ru / mam_mol

$mol - fastest reactive micro-modular compact flexible lazy ui web framework.
http://mol.hyoo.ru
MIT License
676 stars 58 forks source link

Сложное сохранение на сервер #269

Closed zerkalica closed 5 years ago

zerkalica commented 6 years ago

У вас нет примеров, как в mol сделать такое без колбэков? Модуль http вроде как не поможет.

  1. Вводим заголовок, жмем добавить, вызывается метод addTodo
  2. Оптимистично добавляем todo в список на клиенте
  3. Шлем на сервер
  4. Если успех - обновляем в списке todo на такой же с новым id, который сервер вернул
  5. Если фейл - ошибку в todo пробрасываем
class Service {
   @force $: Service
   @mem opCount = 0
   @mem todos: Todo[] = []

   addTodo(title: string) {
        const todo = new Todo({title}, this)
        this.todos = this.todos.concat([todo])
        this.opCount++
            fetch('/api/todo', {
                method: 'PUT',
                body: JSON.stringify(todo)
            })
                .then(toJson)
                .then((updatedTodo: Todo) => {
                    this.opCount--
                    this.$.todos = this.todos.map(
                        (t: ITodo) => t.id === todo.id
                            ? new TodoModel(updatedTodo, this)
                            : t
                    )
                })
                .catch((e: Error) => {
                    this.opCount--
                    this.$.todos = e
                })
        )
    }
}
nin-jin commented 6 years ago

Ну, идеально было бы что-то типа такого:

@ $mol_mem
todos( patch? : Todo[] ) {
    return this.$.$mol_http.resource( '/api/todos' ).json( patch ) as Todo[]
}

С оптимистичным интерфейсом тут есть проблема - как идентифицировать задачу. Ведь пока она не сохранена у неё нет серверного идентификатора. Лучше всего генерировать идентификатор на клиенте и на сервер слать уже по этому идентификатору api/todo/123.

zerkalica commented 6 years ago

По замыслу, у меня uuid генерится на клиенте, а сервер свой подставляет после сохранения. Т.к. id может от базы зависеть.

Не всегда логику сохранения можно упрятать в todos, т.к. от контекста может зависеть.

Например, в случае редактирования другая логика сохранения на сервер:

class Service {
    saveTodo(todo: Todo) {
        this.$.todos = this.todos.map(
            (t: ITodo) => t.id === todo.id
                ? new TodoModel(todo, this)
                : t
        )
            fetch(`/api/todo/${todo.id}`, {
                method: 'POST',
                body: JSON.stringify(todo)
            })
                .then(toJson)
                .then((updatedTodo: Todo) => {
                    this.$.todos = this.todos.map(
                        (t: ITodo) => t.id === todo.id
                            ? new TodoModel(updatedTodo, this)
                            : t
                    )
                })
    }
}
nin-jin commented 6 years ago

У вас анемичная модель, что не очень удобно. Удобнее иметь умную модель:

class $my_domain extends $mol_object {

    @ $mol_mem_key
    todo( id : string ) {
        return $my_todo.make({ id : ()=> id , domain : ()=> this })
    }

    @ $mol_mem_key
    user( id : string ) {
        return $my_user.make({ id : ()=> id , domain : ()=> this })
    }

}
class $my_todo extends $mol_object {

    @ $mol_mem
    domain() {
        return $my_domain.make()
    }

    @ $mol_mem
    data( next? : any ) {
        return this.$.$mol_http.resoutce( `/api/todo/${ this.id() }` ).json( next )
    }

    title( next? : string ) {
        return this.data( next !== undefined ? { title : next } : undefined )
    }

    author() {
        return this.domain().user( this.data().author )
    }
}
$my_domain.make().todo(123).author().todos()[0].title( 'Do it!' )
zerkalica commented 6 years ago

В идеальном приложении норм, но хотелось бы и для анемичной. Если говно-апи уже готовое, не подразумевает сохранение одного todo и ничего с этим не сделать? А сохраняется там черт в ступе, помимо todo.

Контекстов может быть много и пихать их всех в модель это странно весьма. Должны быть инструменты для декомпозиции, а в rich-модели все в кучу.

nin-jin commented 6 years ago

Так для того и нужна умная модель, чтобы приложение не зависело от апи, а предоставляло единый и единообразный интерфейс.

zerkalica commented 6 years ago

Ваш domain похож на Repository, только модель ar. Потом не хотелось бы все сводить к ActiveRecord, в нем есть свои плюсы и минусы. Нарушить SRP и изгадить апи при поддержке проще.

Способов сохранить модель может быть много. На стороне апи может сохраняться только мешанина из моделей. Получается все эти случаи сохранения модели и знания о других моделях надо пихать в нее.

Что будет, если todo сохраняется только вместе с user, например.

PS: А не могли бы вы привести пример полноценного рабочего CRUD с коллекцией, по этому примеру сложно понять. И как ваш пример решает проблему с id, когда надо после сохранения с сервера его взять и заменить в коллекции.

nin-jin commented 6 years ago

И как ваш пример решает проблему с id, когда надо после сохранения с сервера его взять и заменить в коллекции.

Как я говорил, с "призрачными моделями" куча проблем, поэтому либо генерим айдишники на клиенте и тогда новые модели ничем не отличаются от загруженных, либо не кладём новые модели в одну пачку с существующими - храним их во вью в отдельной коллекции, а после сохранения они прибиваются и появляются в "загруженной коллекции".

А что там в круде не понятно? Вообще, да, давно пора запилить $mol_model с типовой реализацией.

zerkalica commented 6 years ago

Атомы накладывают определенные ограничения и вовсе не очевидно, как на них правильно CRUD сделать. Есть много нюансов, чтоб правильно работала реактивность.

Непонятно с тем же opCount из примера выше. Когда надо во время операций писать Saving... или Updating... в компоненте. Как сделать оптимистичное, неблокирующее сохранение. Можно ли работать в анемичном стиле с моделями без колбэков. А если работать в ActiveRecord стиле из самой коллекции todos, тогда как различать новый todo и старый todo, как понять что именно в коллекции обновилось.

Насколько я понимаю, атомы навязывают определенный архитектурный стиль, который может быть не всегда удобен для конкретной задачи или даже непригоден для использования с плохими бэкенд апи.

Поэтому хочется увидеть каноничный CRUD для атомов, хотя бы с некоторыми их этих кейсов, а дальше уже дискуссию от него строить.

Пример ограничения, создаем новый todo, createTodo - сработает по onClick.

class TodoRepository {
  todos: Todo[]

  createTodo(title: string) { 
    const todo = new Todo({title})
    http.response('/api/todo').json({
                method: 'PUT',
                body: JSON.stringify(todo)
            })

    this.todos = this.todos.concat([todo]) 
 }
}

Здесь this.todos = this.todos.concat([todo]) не выполнится никогда. А из кода это не очевидно. Т.е. этот стиль плохо живет с атомами.

zerkalica commented 6 years ago

А как в http правильно делать постобработку полученных данных? fetch -> нормализация -> запись в свойство. Ведь как только я вызываю .json(), выполнение прерывается.

nin-jin commented 6 years ago

Прерывается, а потом перезапускается, но уже не прерывается.

Я сейчас прорабатываю модель для работы с github api. Думаю оно отлично пойдёт как референсный CRUD. Пока без домена - вместо него потом будут контексты.

zerkalica commented 6 years ago

А что за контексты?

nin-jin commented 6 years ago

Который реестр для IoC. this.$ - вот это всё.

nin-jin commented 6 years ago

Запилил круд для дикого REST-API: https://github.com/eigenmethod/mol/tree/master/github

zerkalica commented 6 years ago

Много всего, а приложение готовое есть? Может лучше запилить простой, но эталонный пример, что б сравнить с тем же реактом? Тот же todomvc можно было на круде.

nin-jin commented 6 years ago

http://mol.js.org/app/habhub/

Rurik19 commented 6 years ago

Очепятка? github/auth.ts static token_uri() { return 'http://cors.hyoo.ru/https://github.com/login/oauth/access_token' }

nin-jin commented 6 years ago

В каком месте?

Rurik19 commented 6 years ago

https://github.com/eigenmethod/mol/blob/master/github/auth/auth.ts строка 11

nin-jin commented 6 years ago

Откуда строка я знаю, где в ней опечатка?

Rurik19 commented 6 years ago

два адреса подряд

nin-jin commented 6 years ago

Нет, это CORS Proxy.

Rurik19 commented 6 years ago

Круто

zerkalica commented 6 years ago

А зачем scopes нужен?

habhub - не CRUD, примитивно слишком, todomvc лучше. Еще кстати, Tour of heroes, вполне каноничное приложение.

nin-jin commented 6 years ago

Чтобы запрашивать больше прав при необходимости.

Комментарии сейчас можно получать и добавлять, скоро добавлю, чтобы можно было редактировать (через модель уже сейчас можно, но я хочу в $mol_text добавить поддержку редактирования) и удалять - будет полный круд. Для todomvc пилить бэк не хочется.

zerkalica commented 6 years ago

Зачем полноценный бэк, mock какой-нить влепить на localStorage, вроде такого на fetch-mock.

nin-jin commented 6 years ago

Не люблю писать бесполезный код :-) Вот комментарии - полезная штука и можно увидеть как оно работает в бою, а не на моках. Я их потом сервисом сделаю типа Disqus, чтобы можно было любую страницу комментировать через гитхаб.

zerkalica commented 6 years ago

Что такого, что б мок написать в 150 строк. Он полезный, это ваш текущий todomvc бесполезный, т.к. в реальном приложении есть fetch и асинхронное сохранение через него с обработкой статусов, ошибок, retry и прочей фигней. Это важнее всего, а не то, что там реактивно обновляются свойства, это и mobx может не хуже.

Да и суть в том, что б сравнить с каким-то известным примером, а не с выдуманными вами задачами.

Какая разница, моки там реализуют бэкенд или не моки, код приложения одинаковый будет.

nin-jin commented 6 years ago

todomvc не мой, это http://todomvc.com/

Лента комментариев - вполне распространённый кейс.

В реальном приложении будет ещё CORS, авторизация, несколько десятков ошибок, задержки, кеширование. Всё это можно, конечно, сэмулировать, но всё-равно есть риск, что что-то не учтёшь. С реальным бэкендом все больные места видны сразу.

zerkalica commented 6 years ago

todomvc.com весьма общая спецификация, ваша реализация разве что для бенчей годится, но для демонстрации PoC - нет.

Все эти навороченные приложения с CORS ом, авторизацией должны быть, но это уже не простой вариант. Нужен minimal CRUD и хорошо, если он будет похож на привычный многим todomvc или toh.

Лента комментариев - вполне распространённый кейс.

Ну ок, а можно тогда глянуть аналоги из реакта, ангулара и вуя? Вот toh, например, есть на всех фреймворках: react, vue, angular2

nin-jin commented 6 years ago

toh, на мой взгляд, вообще непонятная хрень. Что это и зачем - не ясно. Вместо дизайна - каша-малаша. Я за понятные, реалистичные и полезные примеры. Чем комменты-то не устраивают?

zerkalica commented 6 years ago

Тем, что не сравнить с другими фреймворками, нет похожих реализаций. Когда архитектурный стиль сильно отличается, мне бы это помогло какие-то аналогии строить.

nin-jin commented 6 years ago

Может подкинуть им идею реализовать что-то полезное, а не заниматься ерундой? :-)

zerkalica commented 6 years ago

Я могу на mobx и react запилить, было бы тз. Можно еще запилить статью на хабр про полезность такой каноничной приложухи с тз, наверняка найдутся, кто в своем болоте запилит.

nin-jin commented 6 years ago

Ок, я запилю ТЗ, когда буду писать статью про $mol_chat.

zerkalica commented 6 years ago

Главное обосновать, что этих кейсов достаточно хотя бы для 70% задач, ведь toh тоже много кейсов показывает, хоть и пример искусственный.

nin-jin commented 6 years ago

Там есть:

  1. Создание.
  2. Редактирование.
  3. Удаление.
  4. Загрузка списка со связанными сущностями (инфа о пользователе в коментарии).
  5. Получение связанных сущностей без дополнительных запросов (уже загружены через другие сущности)
  6. Работа без и с авторизацией.
  7. Запрос авторизации при её протухании.
  8. Обновление списка при добавлении.
  9. Индикатор загрузки списка.
  10. Индикатор сохранения.
  11. Отображение сетевых ошибок.
zerkalica commented 6 years ago
  1. Восстановление после ошибки (retry или что-то вроде)
zerkalica commented 6 years ago

CRUD на основе ActiveRecord это хорошо, но все-таки у вас нет соображений, как реализовать удобный CRUD с анемик подходом?

Когда не "земля копайся", а "экскаватор копай землю", это обычно проще для понимания, хоть и многословнее.

nin-jin commented 6 years ago

Так тоже самое же, только модель будет сервисом-синглтоном, а свойства будут принимать идентификаторы в качестве ключа.