Правильное моделирование приложения MVVM в donejs

Что я сделал

Я создал компонент в donejs, а затем создал две супермодели contact и email, используя следующие команды:

  • donejs add component contactComponent contact-component
  • donejs add supermodel contact
  • donejs add supermodel email

Есть API (feathers+mongodb), который предоставляет контакты и электронную почту. Каждое электронное письмо имеет contactId.

Компонент включает в себя модель Contact и обрабатывает такие вещи, как сохранение, создание нового элемента, удаление элемента и так далее. При объединении этого с файлом .stache он успешно извлечет элементы из API и перечислит их соответствующим образом.

Итак, у каждого Contact есть электронная почта. Поскольку у каждого контакта есть свои электронные письма, contactComponent не может получить их напрямую, но через элемент Contact.

Здесь начинается моя проблема с дизайном.

Пока что contactComonent создает модель представления, которая обрабатывает способ обработки контактов. Контактная модель обрабатывает соединение API. Это отлично работает, является масштабируемым и чистым.

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

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

Я думаю, что у меня сейчас есть что-то вроде этого:

contactComponent.js
├── (includes) models/contact.js
│   ├── (includes) models/email.js

И это то, что я ищу (могу ошибаться)

contactComponent.js
├── (includes) models/contact.js
├── (includes / relates / references) emailComponent.js
emailComponent.js
├── (includes) models/email.js

Используемые файлы

File structure
contactComponent
├── contactComponent.js
├── contactComponent.stache
models
├── contact.js
├── email.js
contactComponent/contactComponent.js
/* contactComponent/contactComponent.js */
import Component from 'can/component/';
import Map from 'can/map/';
import 'can/map/define/';
import template from './contactComponet.stache!';
import Contact from '../models/contact.js';


export const ViewModel = Map.extend({
  define: {
    contactPromise: {
      get: function() {
        return Contact.getList({});
      }
    }
  },
  saveContact: function() {
    // do some stuff
  },
  deleteContact: function() {
    // do some stuff
  }
});

export default Component.extend({
  tag: 'contact-component',
  viewModel: ViewModel,
  template
});
contactComponent/contactComponent.stache
/* contactComponent/contactComponent.stache */
{{#if contactPromise.isResolved}}
  {{#each contactPromise.value}}
    Name: {{name}}
    {{#if emailPromise.isResolved}}
      Emails:
      {{#each emailPromise.value}}
        {{email}}
      {{/each}}
    {{/if}}
  {{/each}}
{{/if}}
models/email.js
/* models/email.js */
import can from 'can';
import superMap from 'can-connect/can/super-map/';
import tag from 'can-connect/can/tag/';
import 'can/map/define/define';

export const Email = can.Map.extend({
  define: {},
  type: null,
  email: null,
});

Email.List = can.List.extend({
  Map: Email
}, {});

export const emailConnection = superMap({
  url: '/api/modelEmail',
  idProp: '_id',
  Map: Email,
  List: Email.List,
  name: 'email'
});

tag('email-model', emailConnection);

export default Email;

Здесь все становится слишком сложным:

models/contact.js
/* models/contact.js */
import can from 'can';
import superMap from 'can-connect/can/super-map/';
import tag from 'can-connect/can/tag/';
import 'can/map/define/define';
import Email from '../models/email.js';

export const Contact = can.Map.extend({
  define: {
    emailPromise: {
      get: function() {
        return Email.getList({ contactId: this.attr('id') });
      }
    }
  },
  name: null,
});

Contact.List = can.List.extend({
  Map: Contact
}, {});

export const contactConnection = superMap({
  url: '/api/modelContact',
  idProp: '_id',
  Map: Contact,
  List: Contact.List,
  name: 'contact'
});

tag('contact-model', contactConnection);

export default Contact;

person nico    schedule 18.03.2017    source источник


Ответы (1)


Не существует единственно правильного ответа на ваш вопрос, но позвольте мне описать, чем я занимаюсь.

Обычно модельные отношения определяются на уровне модели. Насколько мне известно, большинство ORM работают таким образом (например, Mongoose и Sequelize). Я предпочитаю, чтобы отношения были известны обеим сторонам — например, ваша модель контактов знает, что у нее много электронных писем, а модель электронной почты знает, что она принадлежит контакту. Каждая модель может работать сама по себе, то есть вам не обязательно получать электронные письма всякий раз, когда вы имеете дело с контактом, и наоборот.

Затем модели могут предоставлять вспомогательные методы для извлечения связанных данных. Таким образом, ваша модель контактов может реализовать такие методы, как getEmails(), setEmails(), addNewEmail(), doSomethingUniqueWithEmails(). Ваша модель электронной почты может сделать то же самое с getContact() и setContact(). Эти методы будут обрабатывать фактические транзакции данных (выполнение вызовов AJAX), поэтому вам решать, как реализовать эту часть в соответствии с вашими потребностями. Например, когда вы вызываете contact.setEmails([...]), модель контакта установит contactId для всех электронных писем и вызовет EmailModel.save() или что-то в этом роде.

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

Надеюсь, это поможет.

person Ryan Wheale    schedule 22.03.2017