Построение доменно-ориентированого интерфейса на основе Dojo и Javascript

в 12:06, , рубрики: dojo, javascript, Блог компании Luxoft, интерфейсы, метки: , ,

Задача в проекте была ясна и непонятна: заказчик точно знал, где, что и как реализовано в имеющихся вычислительных процедурах, знал он и о насущных требованиях бизнеса. Тем не менее, основным вопросом было и остаётся «что же нужно на самом деле». На этот вопрос мы и пытались ответить с самого начала разработки. «Интерфейс для конфигурирования маржин-калькуляторов» — так называется проект, слова в названии которого по отдельности вполне понятны, но в совокупности не очень. Одной из проблем было то, что представление о маржин-калькуляторе имела всего пара человек на весь проект, а основной ассоциацией на слово «маржа» было слово «автострахование». Сейчас уже можно вздохнуть спокойно, с удовольствием посмотреть на проделанную работу и смахнуть ностальгическую слезу при взгляде на старые мокапы, стряхнуть пыль с архитектурных диаграмм и вспомнить прочтенные страницы документации.

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

Построение доменно ориентированого интерфейса на основе Dojo и Javascript

Описание задачи

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

К примеру, для приобретения акций вам не хватает некоторой суммы денег. Получить их можно разными способами, но нас интересуют взаимоотношения с биржевыми брокерами. При обращении за финансовой помощью брокер рассматривает вашу платёжеспособность и определяет фиксированную сумму «страховки», которая называется маржей. По сути, маржа представляет собой залог, который клиент оставляет брокеру как гарантию того, что занятые деньги будут возвращены. Польза от такого механизма очевидна: для брокера это проценты, рост комиссионных, и т.д., Для клиента — увеличение операционного капитала возможность приобрести больше желаемых активов.

Естественно, при такой торговле для брокера существует риск того, что клиент может крупно проиграться, легко может случиться ситуация (1998, 2007, ...), когда заемщик не сможет вернуть взятые взаймы деньги. Для обеспечения «подушки» от таких инцидентов и был введен механизм маржи, суммы, которую заемщик платит сразу, за возможность использования значительно большего операционного капитала.

Для оценки риска предлагаемой брокеру операции существует множество правил и методик расчёта, как спускаемых «сверху» от регулирующих органов, так и разрабатываемых специалистами банка и их сложность очень разнится — от задания простых неравенств, до достаточно продвинутых методов основанных на статистическом моделировании (о методике Value at Risk я уже писал обзорный пост). В дальнейшем все правила и методики для простоты будут называться калькуляторами.

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

Чем мы занимались

После анализа предметной области, были выведены общие черты класса калькуляторов, которые включали в себя понятия «параметры» и «фильтры». С параметрами было всё относительно просто – они как мёд, либо есть, либо их нет. С фильтрами было всё значительно сложнее – любой из калькуляторов должен уметь принимать на вход только отфильтрованные данные (например, фильтр по стране — финансовые обязательства США и Китая должны оцениваться по разному). Для облегчения построения фильтров нами был разработан механизм динамического составления фильтрующего выражения.

Ниже приведено описание одного их фильтров:

{
   label:"Account",
   type:"AccountFilter",
   attrs:[
      {
         label:"Country of domicile",
         field:"Country1",
         type:"Country",
         tokens:[
            {
               type:"enum",
               idx:0,
               '@type':"simple",
               '@value':"true",
               values:[
                  {
                     label:"in",
                     value:"true"
                  },
                  {
                     label:"not in",
                     value:"false"
                  }
               ]
            },
            {
               type:"widget",
               idx:1,
               '@type':'countryList',
               '@value':[

               ],
               widget:"data.listener.CountryListPropertyEditor"
            }
         ]
      }
}

Данный фильтр описывает проверку регистрации (или её отсутствия) клиента в указанной стране. Подобным образом описываются и другие фильтры, позволяя собирать выражения из кубиков, минимизируя вмешательство в клиентский код при изменении набора фильтров.

Одной из проблем для нас стал жесткий технологический стек, выбранный заказчиком (да, банк не может себе позволить использовать всё, что угодно –процесс утверждения сторонних библиотек достоин отдельной статьи). В итоге, из предложенных технологий мы остановились на Dojo и Spring MVC для работы над интерфейсом.

Другой проблемой явились различые ограничения Dojo, например он не реализует возможность REST по возврату изменённой модели при POST/PUT запросе, что крайне удобно использовать для заполнения дефолтовых значений. Для исправления этой проблемы был реализован метод DeepMerge:

function deepMerge (model, obj, org) {

    org = org || model.toPlainObject();

    // delete fields that are not in updated objects
    if (lang.isArray(org)) {
        for (var i = org.length - 1; i >= 0; --i) {
            if (null === obj || !(i in obj)) {
                if (typeof model.get !== "undefined") {
                    model.remove;
                }
            } else {
                deepMerge(model.get, obj[i], org[i]);
            }
        }
    } else if (org instanceof Object) {

        for (var i in org) {
            if (org.hasOwnProperty(i)) {

                if (null === obj || !(i in obj)) {

                    // Don't try to remove `undefined` property from model
                    // this may happen when we create model with explicitly set undefined property
                    // such property will be caught by `for in` loop
                    // JSON.stringify will eliminate this property
                    // so it won't be present in response
                    // but removing undefined, but existent property
                    // from model will result in error :(
                    if (typeof model.get(i) !== "undefined") {
                        model.remove(i);
                    }
                } else {
                    deepMerge(model.get(i), obj[i], org[i]);
                }
            }
        }
    }

    if (lang.isArray(obj) || obj instanceof Object) {
        for (var i in obj) {
            if (obj.hasOwnProperty(i)) {

                if (!(i in org)) {

                    model.set(i, new StatefulModel({
                        data: obj[i]
                    }));

                } else {
                    deepMerge(model.get(i), obj[i], org[i]);
                }
            }
        }

    } else {
        model.set('value', obj);
    }
};

Вместо послесловия

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

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

  • Использование NoSQL хранилищ для быстрого доступа к данным по рынку
  • Алгоритмы и вычисление рисков
  • Клиринговые центры и их роль в финансовом мире

P.S. У нас куча интересных и не тривиальных задач, если вам интересно, то ждем к нам в гости.

Автор: Silf

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js