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

Что я сделал

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

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

Есть API (перья +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

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

Файловая структура
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}}
Модели /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;

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

Модели /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;

1 ответ

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

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

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

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

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

Другие вопросы по тегам