слои архитектуры мобильного приложения включают в себя

Разбираем ELM архитектуру в рамках мобильного приложения

Это вторая часть серии статей об архитектуре android приложения vivid.money. В ней мы расскажем в деталях о том, что из себя представляет ELM архитектура. В связи с тем, что наша реализация доступна в open source в качестве библиотеки Elmslie, в статье будет использоваться нейминг из нее.

Оглавление

Вступление

Одной из основных проблем в разработке мобильных приложений с использованием MVP/MVVM/MVC паттернов становится раздутие презентеров. Часто в них скапливается абсолютно все управление асинхронной работой и состоянием приложения. С течением времени, усложнением логики и общим ростом кодовой базы их становится невероятно трудно менять. А понимание того что происходит в написанном другим разработчиком презентере может стать непосильной задачей и исправление багов лишь привносит больше новых ошибок.

О чем эта статья?

ELM архитектура достаточно общее решение, для которого есть множество применений. Однако, чтобы упростить понимание, опишем ее на примере реализации слоя представления. Разобравшись с поведением архитектуры на простом примере, можно поискать и другие применения.

слои архитектуры мобильного приложения включают в себя. image loader. слои архитектуры мобильного приложения включают в себя фото. слои архитектуры мобильного приложения включают в себя-image loader. картинка слои архитектуры мобильного приложения включают в себя. картинка image loader.Место ELM в архитектуре

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

В описании мы не будем останавливаться на модели, рассказывать о том как писать бизнес-логику, делать запросы к API. Про организацию кода во View тоже не будет ни слова, эти моменты остаются на ваше усмотрение.

Слой представления снаружи

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

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

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

Взаимодействие с View устроено немного сложнее. У него есть три составляющие:

У экрана есть некоторое состояние. В него может входить информация о том, показывается ли сейчас состояние загрузки, данные для отображения в списке или текущее положение toggle switch. Все это включим в термин State.

А теперь внутри

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

Обработка событий в пользовательском интерфейсе (Event.UI)

Изменение состояния экрана (State)

Запуск операций в UI (Effect)

Получение данных из модели

Запуск операций в модели

слои архитектуры мобильного приложения включают в себя. image loader. слои архитектуры мобильного приложения включают в себя фото. слои архитектуры мобильного приложения включают в себя-image loader. картинка слои архитектуры мобильного приложения включают в себя. картинка image loader.Разделение работы экрана

Опишем сущности, которые у нас получились:

Actor

В Actor находятся все асинхронные операции, вычисления и работа с моделью. Опишем это с помощью Command, которая в общем случае запускает некоторую операцию и Event, который вернет результат этой операции.

Подписка на обновление данных в модели

Выполнение запроса к API

Запуск таймера на выполнение операции

Reducer

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

Result

Выделим так же отдельную сущность, которая будет представлять ту самую реакцию Reducer на Event и назовем ее Result.

Собираем все вместе

Если объединить все эти компоненты получится примерно следующая картина:

слои архитектуры мобильного приложения включают в себя. image loader. слои архитектуры мобильного приложения включают в себя фото. слои архитектуры мобильного приложения включают в себя-image loader. картинка слои архитектуры мобильного приложения включают в себя. картинка image loader.Внутренности Store

View и Actor являются источниками событий. Это представлено в виде Event. События разделяются по типу источника, для View это Event.Ui, а для Actor это Event.Internal. События побуждают изменения состояния экрана, одиночные эффекты, а также запуск асинхронных операций. Состояние экрана представлено State, которое доставляется View для отрисовки. Одиночные эффекты обозначены как Effect и так же обрабатываются View. Actor в свою очередь работает с моделью, запускает операции и получает из нее данные. А Store связывает все это вместе.

Как это работает?

Сценарий успешной загрузки

Разберем сценарий, когда при нажатии на кнопку значение успешно загружается и отображается в UI.

слои архитектуры мобильного приложения включают в себя. image loader. слои архитектуры мобильного приложения включают в себя фото. слои архитектуры мобильного приложения включают в себя-image loader. картинка слои архитектуры мобильного приложения включают в себя. картинка image loader.Работа ELM во время успешного сценария

    Пользователь нажимает на кнопку Reload

    UI отправляет Event.UI обозначенный CLICK

    CLICK приходит в Reducer

    Результатом работы Reducer становится изменение isLoading на true в State и отправка Command обозначенная как LOAD

    Из-за изменения State в UI отрисовывается текст LOADING.

    Результатом выполнения команды становится Event.Internal со значением VALUE

    Reducer обрабатывает событие VALUE и изменяет в State у поле value значение на 123, а у поля isLoading на false

    В UI отрисовывается текст VALUE = 123
    Сценарий неуспешной загрузки

    А теперь неуспешной

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

    слои архитектуры мобильного приложения включают в себя. image loader. слои архитектуры мобильного приложения включают в себя фото. слои архитектуры мобильного приложения включают в себя-image loader. картинка слои архитектуры мобильного приложения включают в себя. картинка image loader.Работа ELM в случае ошибки

      Пользователь снова нажимает на кнопку Reload и отправляется Event.UI обозначенный CLICK

      CLICK приходит в Reducer

      Результатом работы Reducer становится изменение isLoading на true в State и отправка Command обозначенная как LOAD

      Из-за изменения State в UI отрисовывается текст LOADING.

      Результатом выполнения команды становится Event.Internal со значением ERROR

      Reducer обрабатывает событие ERROR и изменяет в State значение у поля isLoading на false, а также отправляет Effect под названием ERROR

      UI обрабатывает Effect обозначенный ERROR и показывает Snackbar с ошибкой

      В итоге

      ELM архитектура пришла из веба и пока не столь популярна в мобильном сообществе. Однако она определенно заслуживает внимания, наравне с более привычным MVI, благо в них не так много различий. По сравнению с популярными MVP и MVVM она удобнee в тестировании, позволяет писать более простой код и лучше масштабируется. Подробнее о причинах нашего выбора ELM архитектуры мы рассказывали в предыдущей части серии.

      Поскольку существующие реализации ELM показались нам недостаточно лаконичными и простыми в использовании мы создали Elmslie. Мы постарались вобрать достоинства существующих реализаций, максимально упростив написание кода. В следующей части мы расскажем о том, как пользоваться нашей библиотекой.

      Источник

      Архитектурный слой (в корпоративной разработке). Понятие, определение, представление

      Сейчас сложно найти короткое и понятное определение таких понятий как «слой приложения» и «уровень абстракции». Это влечёт различия в понимании одного и того же либо непонимания данного понятия среди разработчиков. А недопонимания ведут к разногласиям.

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

      Архитектурный слой (корпоративного приложения) — это определённый (огранниченный назначением, замкнутый) набор ресурсов (инструментов работы с ресурсами, деталей, составляющих), с помощью которых реализуются множество (огранниченное критериями) прикладных задач, характерных для данного слоя. Вышестоящий слой реализует свои составляющие (ресурсы), на основе ресурсов нижестоящего слоя. Ресурсы определённого слоя совместимы друг с другом и используются только внутри этого слоя (в идеале). Вышестоящий слой может созадавать свои ресурсы используя ресурсы нескольких слоёв. Может иметь отношение к абстрактной, идеализированной системе, сущетвующих в виде схем либо в большей мере относится к реализации — это определяет составляющие слоя.

      Стоит задача автоматизировать электронный документооборот, которая подразумевает

      1 уровень — задачи по строительству здания, фундамент, возведение стен, кирпичи, цемент
      2 уровень — отделка мебелирования, детали — обои, мебель…
      3 уровень — логическое распределение помещений, людей, деление на отделы — детали: отделы, рабочие места, помещения

      Составляющие (реусуры) слоя, включают в себя объекты (кирпичи, плиты, цемент) и действий над ними (положить, установить).

      Один слой предоставляет только набор тех ресурсов, которые ему логически характерны. 3й слой (места, кабинеты, отделы), работает только в рамках этих сущностей и действий. Если мест не хватает, то кабинет не достраивается, но его можно достроить, доработав ниже стоящий слой.

      Источник

      Аспекты удачной архитектуры мобильных приложений

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

      Вот только потом, когда встает вопрос о поддержке, рефакторинге и введении новых фич, оказывается, что в контроллерах у нас тонны кода, количество boilerplate застилает 4к экран, а вкорячивать новые фишки сложнее, чем переписать все снова. И вот вы уже снова перепиливаете все в стиле *уяк-*уяк и в продакшн…

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

      Под катом ответы на распространенные вопросы и некоторые советы, которые помогут создать качественный продукт.

      Встречайте!

      слои архитектуры мобильного приложения включают в себя. image loader. слои архитектуры мобильного приложения включают в себя фото. слои архитектуры мобильного приложения включают в себя-image loader. картинка слои архитектуры мобильного приложения включают в себя. картинка image loader.Евгений Мацюк. Ведущий разработчик «Лаборатории Касперского». Имеет огромный опыт внедрения новых подходов и инструментов.

      слои архитектуры мобильного приложения включают в себя. image loader. слои архитектуры мобильного приложения включают в себя фото. слои архитектуры мобильного приложения включают в себя-image loader. картинка слои архитектуры мобильного приложения включают в себя. картинка image loader.Александр Блинов. В REDMADROBOT занимается архитектурой Android, вопросами технологизации команды и непосредственно разработкой приложений.

      слои архитектуры мобильного приложения включают в себя. image loader. слои архитектуры мобильного приложения включают в себя фото. слои архитектуры мобильного приложения включают в себя-image loader. картинка слои архитектуры мобильного приложения включают в себя. картинка image loader.Антон Руткевич. Работая в «Яндексе», разрабатывал приложения, занимался нетривиальными случаями их сборки и процессами непрерывной интеграции. Сейчас в Juno занимается разработкой Android приложений на Kotlin.

      Об архитектуре, планировании и команде

      — Чем отличается удачная архитектура от неудачной?

      Евгений Мацюк: Удачная архитектура дает шанс разработчикам никогда не проводить глобальный рефакторинг всего приложения. Максимум — изолированно переделать некоторые модули без долгой починки появившихся багов.

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

      Александр Блинов: Архитектурные ошибки весьма коварны. Первые несколько месяцев разработки они прячутся за вашей спиной и иногда напоминают о себе костылями и подозрительным кодом. С ростом кодовой базы они начинают фигурировать все чаще, увеличивая энтропию вашего кода. Со временем внедрение новых фич несет изменения в самых неожиданных местах программы. Такие ситуации негативно влияют на настрой команды и существенно бьют по бизнесу.

      — Какой, на ваш взгляд, оптимальный состав команды для разработки приложения? Сколько проектировщиков могут быть заняты разработкой архитектуры? Как выстраивать их взаимодействие для достижения максимального эффекта?

      Евгений Мацюк: Если мы говорим о разработке приложения под конкретную платформу, то обычно все сводится к следующей схеме. Команда — 3-5 разработчиков разного уровня и тимлид, на котором висят задачи планирования и взаимодействия со всем внешним миром. Непосредственно архитектурными задачами может заниматься как сам тимлид, так и, например, самый сильный разработчик из команды. То есть за архитектуру ответственен один, максимум два человека. Все ключевые решения по проекту — только через них. Никто, конечно же, не отменяет холиваров внутри команды, но ответственность всегда кто-то должен нести.

      Александр Блинов: Команды с большей численностью должны быть разделены на отделы, работающие над разными частями приложения. Деление на части может быть как горизонтальным, так и вертикальным. Порядок взаимодействия логических юнитов приложения должен определять архитектор. В его обязанность также входит общее проектирование системы. За соблюдением этих практик следит лид каждого из отделов.

      Антон Руткевич: На мой взгляд, это сбалансированный коллектив, где есть люди с разными ролями. Безусловно, все должны хорошо разбираться в языке, платформе, архитектуре, но отличные команды получаются, когда у каждого есть какая-то любимая область в разработке в которой он разбирается особенно хорошо, «специализация» что-ли, и эти специализации не имеют большого перекрытия в команде (хотя какое-то, конечно, имеют). Специализациями могу быть, например, высокоуровневая архитектура, работа с UI или другими фреймворками платформы, CI, тестирование, дизайн. Это не значит, что только «специалисты» занимаются этими областями, но обычно именно они двигают вперед, приносят улучшенные подходы, развивают свои области. Поэтому чем больше областей охвачено — тем в больших аспектах проекта будет порядок. Если вся команда болеет за архитектуру и не любит копаться в UI фреймворке — есть большой шанс иметь частые архитектурные холивары и сделанные «кое-как» UI слои.

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

      И еще. Если в команде нет человека, без которого разработка встанет, то очень просто уходить в отпуск, брать day-off при необходимости. Вы не представляете, как приятно работать в команде, где всегда знаешь, что коллеги подхватят твои задачи при необходимости, и это не будет проблемой.

      — Какой объем времени должен быть выделен на планирование и архитектуру приложения в сравнении с остальными этапами разработки?

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

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

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

      Роль сервера

      — Можно ли объяснять проблемы в архитектуре мобильного приложения некачественным API сервера? Или же это независимые вещи?

      Евгений Мацюк: Правильной архитектурой удастся инкапсулировать некачественное API сервера.

      На моей практике был такой случай. Я занимался внедрением модуля чата в банковское приложение. Работа чата осуществлялась посредством стороннего сервиса. Модуль я реализовал по концепции «Чистой архитектуры», покрыв все юнит-тестами. Сделал и забыл.

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

      И вот тогда я испытал то бесценное чувство, когда заложенная архитектура «просто тащит». Это бесценный момент. Всем желаю такое испытать. Круче первого поцелуя.
      На уровне Репозитория подмена не получилась, так как концепция нового сервиса просто другая. Поэтому подмена была осуществлена на уровне Интерактора. Да, мне пришлось реализовать новый Репозиторий и новую имплементацию Интерактора. Но вьюшку и презентер я вообще не трогал, а там много кода тоже было. Подмена сервисов чата легко осуществлялась через дагеровский модуль (просто подставляли нужную реализацию Интерактора).

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

      — Как вы относитесь к методологии TDD? Какой подход к разработке вы используете чаще всего и почему?

      Антон Руткевич: TDD — отличная методология. Если следовать ей, проблема покрытия кода тестами исчезает как класс, т.к. без тестов кода просто нет.

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

      Поэтому мы используем TDD в основном когда нужно расширять существующий функционал и интерфейсы более менее ясны.

      — Когда оправдан перенос логики выполнения из приложения на сервер?

      Александр Блинов: Мобильное приложение является тонким клиентом и не предназначено для выполнения серьезной логики. Если заказчик не готов предоставить мобильному клиенту API, соответствующий данному подходу, мы встраиваем в цепь взаимодействия клиент-сервер middleware. Middleware — это наш прокси-backend, который берет на себя интеграцию со сложными сервисами заказчика и приведение API для клиентского приложения к удобному виду. Как правило, это ускоряет разработку, так как сложная логика пишется один раз на мидле а не два раза: на Android и iOS. В некоторых случаях встраивание в цепь middleware невозможно, например мобильные банки имеют повышенные требования к безопасности и хотят свести поверхность возможной атаки к минимуму. В этих случаях мы интегрируем наш middleware в сервера заказчика.

      — Какое место занимает MVP, MVC и MVVM в архитектуре мобильных приложений?

      Евгений Мацюк: Термин MVC звучит уже как-то не модно среди андроид-разработчиков. Сразу же срабатывает ассоциативный ряд с Activity, которое раньше считалось Controller и достигло нереальных размеров и сложности. Вспомнили? Передернуло от ужаса? Не произносите эту аббревиатуру вслух больше никогда, иначе будете преданы анафеме.

      MVP, MVVM — это больше про Presentation слой. Причем набирает популярность в последнее время именно MVVM, но реализованный не через DataBinding, а «руками» через RxJava. В итоге получается меньше boilerplate кода, чем с MVP. С удовольствием послушаю доклад Дениса Неклюдова и Степана Гончарова про данный подход.

      А «Clean architecture» — это про все приложение в целом.

      О структуре Android-приложений

      — В чем особенность разработки архитектуры приложения для android?

      Евгений Мацюк: Вопрос, о котором можно говорить вечно. По мне самая главная особенность — это то, что сначала об архитектуре, как таковой, вообще не думали. Гугл в свое время предоставил SDK и сказал разработчикам: «В добрый путь, ребята». И ребята пошли, но не совсем по доброму пути. В итоге появилось большое количество приложений, которые приходится поддерживать и переделывать. Зато нам никогда не скучно.

      Александр Блинов: Разработчиками Android было принято много довольно странных решений, таких как: жизненный цикл, способ предоставления пермишенов. В первых версиях системы такие решения были продиктованы низкой мощностью железа. В настоящее время смартфоны стали куда более мощными, однако мы стали заложником лозунга: «Написали однажды — страдаем всегда!». Это порождает создание фреймворков, инкапсулирующих в себя борьбу с системой, таких как наш с Senneco Moxy. Советую послушать рассказ Юры на Мобиусе о том, как устроено Moxy под капотом

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

      Александр Блинов: Это довольно холиварный вопрос. Пять логических слоев, которые мы выделяем в «Чистой архитектуре», имеют свою определенную область ответственности. Мы придерживаемся консистентности в архитектуре и, даже если в какой то фиче потребность в логическом слое отсутствует, он будет выступать в качестве прокси. Это делает код предсказуемым и легко расширяемым.

      — Какой стек технологий обычно применяется для проектирования приложения под Android?

      Евгений Мацюк: В прошлом своем выступлении я рассказывал про следующий стек: RxJava, Dagger, Unit-тесты. Сейчас добавил бы Moxy — для обработки жизненного цикла, Cicerone — для навигации. А вообще еще можно начать писать на Kotlin.

      Александр Блинов: В REDMADROBOT аналогичный стек технологий. Kotlin у нас в продакшене находится больше года. Весь новый код пишется только на Kotlin.

      — Какой бы главный совет вы дали разработчикам в плане построения архитектуры?

      Антон Руткевич: Я бы сказал, что нужно помнить о SOLID и осознанно применять подход Clean Architecture. Это не значит, что нужно параноидально следовать ему везде, но отход от Clean Architecture в каких-то местах всегда должен быть осознанным и взвешенным решением.

      Евгений Мацюк: Думайте. Пробуйте. И еще раз думайте. И еще раз пробуйте.
      Обязательны изучите основные паттерны проектирования и проникнитесь, зачем и в каких ситуациях они действительно нужны.

      Создание удачной архитектуры — самое сложное и ответственное занятие. Но тут действительно уместна поговорка — «терпение и труд — все перетрут».

      — О чем вы будете рассказывать на Мобиусе?

      Евгений Мацюк: Мы хотим произвести погружение в «Чистую архитектуру». Рассмотрим основные вопросы, которые задают разработчики, рассмотрим кейсы, которые часто встречаются в нашей жизни, и покажем, как их решать в «Чистой архитектуре».

      К сожалению, у нас только 50 минут, и мы не расскажем и малой доли того, что накопилось у нас интересного. Но мы работаем над документом (это будет или серия статей, или CookBook), в которой постараемся раскрыть «Чистую архитектуру» от базовых теоретических моментов до самых изощренных практических ситуаций. Поэтому мы будем рады любой помощи, любому совету, любой интересной задаче. С января нами запущен чат в «Телеграме», посвященный архитектуре в Андроид. Нас уже более 500 человек. Присоединяйтесь и задавайте свои вопросы!

      Антон Руткевич: На Мобиусе расскажу о том, как осознанно писать код так, чтобы после написания его гарантированно можно было покрыть тестами. Несмотря на то, что в докладе будут примеры на Kotlin (чтобы код влезал в слайд) и Android, акцент будет на идеях, которые лежат вне платформ и языков, и на практическом умении применять их в любом контексте. Поэтому приглашаю всех.

      Александр Блинов: Многие технологии отлично работаю на Helloword-ах, но столкнувшись с реальным кодом терпят фиаско. В своем докладе мы разберем самые сложные кейсы, на которых «Чистая архитектура» доказала свою состоятельность!

      Поэтому хочется пригласить всех на наш с Евгением доклад в рамках Mobius 2017. Обещаем погружение в Чистую архитектуру и славный холивар с товарищами по цеху! Также желаем каждому из вас выйти с конференции вдохновленным, с горящим глазами и крутыми идеями!

      Источник

      Добавить комментарий

      Ваш адрес email не будет опубликован. Обязательные поля помечены *