Русский
Русский
English
Статистика
Реклама

Service

Из песочницы Как выбрать ИБП

13.06.2020 14:36:10 | Автор: admin
Я рад всех видеть и хочу поделиться своими наблюдениями и опытом в области ИБП.

Что такое ИБП, мы все знаем. Для чего они нужны, вроде бы тоже понятно (во всяком случае хочется в это верить).

И так настал тот час, когда нам надо купить ИБП, вот тут и начинается головная боль.

Какой выбрать?
На что смотреть?

  • Именитость бренда?
  • Производитель со столетней историей?
  • Количество инсталляций?
  • Стоимость?
  • У коллеги есть такой же?

image

Давайте разбираться.

Именитость бренда


При слове ИБП моментально вспоминаем компанию APC эта аббревиатура прочно ассоциируется с ИБП, особенно в России. И действительно, кто не знает это?

image

Или компанию Legrand, между прочим основанную в 1860 году. А компания APC была основана в 1981 году. Первый свой ИБП компания APC выпустила в 1984 году, который она разработала после прекращения финансирования государством разработок в области солнечной энергетики. Компания же Legrand выпустила свой ИБП после 2010 года и то, потому что в 2010 году купила компанию MetaSystem.

Компания APC молодцы безусловно, только до 2007 года компания APC не выпускала 3х-фазные мощные ИБП. Более того APC никогда не выпускала 3х-фазные ИБП. В 2006 году компанию APC поглотила Schneider Electric, не менее уважаемая компания, основанная аж в 1836 году во Франции. В начале своего пути компания называлась Schneider и занималась исключительно выпуском вооружения для армии Франции и на экспорт, в плоть до окончания второй мировой войны. Лишь в послевоенное время переорентировалась в сторону электротехнического рынка, добавив в название Electric. (интересный факт: Многие артилерийские орудия Российской империи были закуплены именно у компании Schneider)

Так вот компания Schneider Electric в 2006 году приобретает компанию APC, с твердым намерением скрестить усилия с MGE, так же удачно приобретенной в 2007 году тем самым прочно занять нишу на рынке. Но Европейский антимонопольный комитет не одобрил сделку (хоть что-то европейцы не одобрили в тему) и разделил компанию MGE между двумя покупателями. 3х-фазное направление отошло в Schneider Electric и было интегрировано в продуктовую линейку APC by Schneider Electric, а однофазное направление забрала EATON (основанная в 1911 году в США и изначально специализировавшейся на производстве автомобильных мостов для грузовиков), который так же подсуетился и выкупил Powerware, объединившийся незадолго до этого с Best Power, с их мощными 3х-фазными системами.

Мне очень нравится история компаний, говорить я могу о ней долго и самозабвенно, перебирая яркие страницы истории брендов PowerCom, CE+T, Riello, Socomec, Newave продавшийся в ABB, немного в стороне стоят Delta и Huawei их историю надо рассказывать отдельно, не забыв при этом Kehua.

image

Чуточку задержусь на GE (основана в 1878 году США и это единственная компания, которая начинала именно с электро-энергетики, наверное потому что они ее и сделали электро-энергетику) эти перцы были на передовой все время, собственно Эдиссону и Тесла, а точнее их противостоянию мы обязаны всем тем что нас сейчас окружает: генерацией и транспортом электроэнергии. Но сейчас не об этом. GE весьма успешна, потому что всегда умела сосредоточиться на главном, а не нужное продать. Например продажа всего сегмента разработки и производства электронно-вычислительных машин компании Honeywell в 1971 году как убыточного направления. Или продажа всего сегмента ИБП компании ABB в 2017 году.

Давайте закончим с историей и подведем итог.

Стоит отметить 2 наших возможных фактора выбора:

  • Именитость бренда
  • Производитель со 100-летней историей

Потому что это абсолютно не важно! Сами ИБП, в том виде как мы привыкли их видеть появились совсем не давно конец 90х начало 2000х и за этот маленький период времени. И за этот маленький период времени почти все на рынке успели по перепродавать и по пере покупать друг друга. Коренным образом не разрабатывая технологию, а только лишь встраивая ее в свою продуктовую линейку.

Били ли ИБП до 80-х годов? Да были, только они так не назывались. Они были механическими так же известны как, 3х-машинные преобразователи и они скорее прародители современных Дизель Динамических ИБП. Во всяком случае принципы одинаковые. Поэтому заявления производителей ДДИБП про уникальность и инновационность своих разработок не до конца является правдой.

Переходим к следующему.

Количество инсталляций


Количество инсталляций хороший фактор он показывает доверие к оборудованию (как нам объясняют производители) Скажу вам честно НИЧЕРТА ОН НЕ ПОКАЗВАЕТ, кроме успешной работы отдела продаж, но можно всегда посмотреть на то что покупаешь в естественной среде обитания, может даже послушать местную службу эксплуатации про то как эта машина беспроблемно работает на их объекте любопытно, но не более. Потому что Вас затягивают в субъективную сторону оценки и субъективный способ выбора.

Стоимость


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

Так на что же опираться?

Куда смотреть?

image

В свое время, я оказывался в такой ситуации не раз. И каждое решение было как действия сапера на минном поле. А в ЦОДах так не принято делать. Принцип Слабоумие и отвага не приносит результата в долгосрочной перспективе.

Размышляя об этом я пришел к очень простому выводу идеального ИБП не существует и хорошего ИБП тоже не существует. Они все одинаковые! (это не буквальное определение, все таки отделы разработок не зря кушают свой хлеб с красной икрой у вендоров) Нужно лишь выбрать тот ИБП, который тебя устроит именно здесь и сейчас.

А тогда чем руководствоваться, спросите Вы?

Сейчас я постараюсь Вас к этому подвести.

Недавно я проходил обучение в Uptime Institute и в прологе к курсу была одна фраза:

image

Начиная путь, ты должен четко представлять где ты хочешь оказаться

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

Возвращаясь к ИБП, что для нас, как для будущих владельцев сего прекрасного поделья важно? Как можно меньше испытывать проблем с нашим ИБП.

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

Пример:

Один известный итальянский бренд, точнее русский OEM известного итальянского бренда. Хорошая цена ИБП, много кто продает, очень много инсталляций, в том числе и в СПБ, но нет инженеров, которые могут его чинить, реально разобрать до болтика, найти неисправность и устранить. А потом настроить правильно и оптимально под конкретные условия и место работы после спецов по ПНР.


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

Второе, на что надо обратить внимание это запчасти и расходники. А именно стоимость, наличие, условие продажи доставки. Это очень важно!

Пример:

Один известный китайский бренд. Поставлял мне свой ИБП. Новая модель ИБП. Первый экземпляр, ввезенный в Россию. Завершается ПНР, машина сдается и оставляется на холостом ходу на выходные. В понедельник наладчики приходят к выключившейся с ошибкой машине и брошенным выходом. После пары телефонных звонков и 14 часов ожидания приезжает объем запчастей, достаточный для сбора еще 2х ИБП. Приезжает сразу на объект.


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

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

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

Пример:

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


image

Подводя итог, хочу обратить пристальное ваше внимание, что ответив на эти простые вопросы об ИБП. И убедившись, в том что все эти вопросы получили ответы, которые вас устроили. Вы можете точно сказать, что проблем с конкретным оборудованием у вас не будет. Точнее они будут и очень много, но вы всегда сможете их решить быстро и качественно!

Обращайте на это внимание, выбирайте оборудование с умом!
Подробнее..

Методологический скачок от таблиц-портянок к понятному каталогу услуг в ITSM-системе

14.10.2020 14:07:29 | Автор: admin


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

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

Эволюция подхода к созданию сервисного каталога


В клиентских проектах мы пробовали разные подходы к составлению сервисных каталогов.

Таблицы в Word. Еще несколько лет назад по каскадной модели разработки (Waterfall) скрупулезно собирали информацию в текстовые файлы. В этих документах фиксировали всё от наименования услуг, основных ответственных до видов деятельности по определенной услуге и SLA.

image

Пример табличного описания услуги Электронная почта в формате текстового документа

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



К концу аудита услуг заполненные таблицы могли исчисляться десятками


Мало того, что информация корректируется на ходу, так и сам набор параметров с каждым обновлением таблицы пухнет и пухнет. Ведь по услуге недостаточно знать SLA и ответственность. Лучше помнить всех получателей поименно, а также выяснить, какие ресурсы инфраструктуры услуга предоставляет и от каких ресурсов зависит.

В то же время в любой компании процессы не статичны. Появляются новые сервисы, развиваются существующие. Услуги обрастают огромным слоем объектов: запросами, справочниками, связями, параметрами, КЕ.

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

Таблицы в Excel. Взамен многостраничных документов в нашу практику ворвались другие таблицы. Описательную часть для клиента мы по-прежнему оставляли текстом. А к нему прикладывали таблицу со списком услуг и их параметрами. Сложить нужную информацию в один документ опять не получалось. По факту каталог услуг вырастал в сеть взаимосвязанных таблиц.



Сопровождать в Excel множество вкладок и столбцов вручную неудобно. На эту работу аналитик внедрения тратил сотни часов


Такой способ составления списка услуг нами рассматривался как почва для первичного импорта в систему автоматизации. Далее развитие и оптимизацию планировали вести уже в ИТ-системе. Но и этот подход оказался не без минусов.

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

Другое дело динамический каталог, который изначально проектируется со своей структурой и ценностью в инструменте автоматизации. Подобный каталог начинает функционировать только с появления в нем первой услуги. Нельзя на этапе внедрения разработать каталог услуг под ключ. За этот период удается запустить процесс по его наполнению и актуализации. А значит оптимальный подход собрать минимум информации и сразу идти в ИТ-систему.

В чем ценность каталога услуг



Пользу грамотно спроектированного сервисного каталога почувствуют все участники процесса.

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



Интерфейс Портала самообслуживания с удобной группировкой услуг. Реализован на базе платформы Naumen SMP

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

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

Владельцам и Заказчикам интересны параметры качества и финансов.

Какие шаги помогут создать каталог услуг в ИТ-системе


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

Шаг 1. Определение цели


Главный вопрос в любой деятельности зачем?. Для себя цели сервисного каталога сформулировали так:

  1. Организовать продуктивное взаимодействие с получателями услуг.
  2. Использовать единую платформу для построения ITSM-процессов и применения сервисного подхода во всех подразделениях компании.
  3. Заложить основу для управления и развития всех бизнес-процессов.

Шаг 2. Проведение обследования


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

В наше время с нуля это как вернуться в каменный век или попробовать заново изобрести велосипед. Не стоит отказываться от наработок. Берем все, что есть, и упаковываем в ИТ-систему.

Если каталог услуг в компании не формализован, анализируем такие артефакты:

  • журнал обращений;
  • (если используются) классификаторы и справочники для заявок;
  • список информационных систем и оборудования;
  • оргструктуру;
  • сервисные контракты с внешними поставщиками.

Далее алгоритм следующий:

  1. Выделяем популярные вопросы к сервисным службам.
  2. Формируем набор услуг на понятном пользователю языке.
  3. Группируем и приземляем обращения на управляемые ресурсы.
  4. Предлагаем наименование услуги, которое увидит пользователь в ИТ-системе при подаче обращения.



Пример корреляции: Обращение пользователя-Оборудование-Услуга в ИТ-системе


Изначально в каталоге стоит предусмотреть услугу Прочее/Заявка в свободной форме. Негласно ее называют Канал подачи мусора. При периодическом анализе подобных обращений можно определить:

  • неудачно сформулированные наименования услуг. Например, конкретная услуга присутствует в каталоге, но пользователи ее не находят и выбирают универсальную;
  • потребность в обучении персонала. Например, пользователи конкретного отдела систематически указывают услугу Прочее, в то время как большинство подразделений выбирают корректные классификаторы;
  • тенденции и потребности в развитии сферы услуг. Например, поступает большой объем повторяющихся обращений на услуги, которые не предоставляются сервисной организацией.

Шаг 3. Распределение ответственности


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

Важно определить уровни ответственности:

  • Исполнитель обращений.
  • Менеджер услуги.
  • Менеджер каталога.

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

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

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

  • Руководитель проекта внедрения ПО. Видит картину в целом и может принимать управляющие централизованные решения.
  • Руководитель сервисной организации (ИТ-департамента). Наиболее заинтересован в развитии взаимоотношений заказчиков и исполнителей, владеет знаниями стратегии сервисной организации и знает, как они коррелируют со стратегией основного бизнеса.
  • Группа компетенций. Ответственность закрепляется не за конкретным специалистом, а формируется на основе знаний участников проекта внедрения: РП, главных консультантов, сотрудников, которые способны передавать знания другим участникам.

Шаг 4. Подготовка каталога


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

Здесь сразу делимся нашими наработкам: какие параметры на практике оказались наиболее востребованными и зачем они нужны.



Готовый перечень параметров услуги. Скачать полную версию

Результатом формирования каталога услуг в ИТ-системе становится качественный классификатор, при этом еще обновляемый и управляемый.

Шаг 5. Развитие каталога


Важный этап развития каталога услуг настройка связи с ресурсно-сервисной моделью (РСМ). Ее проектирование и поддержка может поглощать бесконечные трудоресурсы. Но пользы от нее гораздо больше.

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

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



Пример части каталога услуг, который одновременно служит классификатором КЕ


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

Выбор архитектурного стиля (часть 2)

17.09.2020 12:16:31 | Автор: admin
Привет, хабр. Сегодня я продолжаю серию публикаций, которую написал специально к старту нового потока курса Software Architect.



Введение


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

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

В этот раз я предлагаю поговорить о возможностях организации системы в виде набора модулей/библиотек (компонентно-ориентированная архитектура) или сервисов (сервис-ориентированная архитектура).

Компонентно-ориентированная архитектура


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

При грамотном использовании компонентов решается проблема большого кома грязи (большой размер + высокая связанность), а сами компоненты могут представлять из себя как единицы сборки (модули, библиотеки), так и единицы развертывания (сервисы). Единицы развертывания не всегда отображаются на выполняемый процесс: например, веб-приложение и база данных развертываются совместно.

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

Самая главная проблема такого монолита заключается в том, что разделение на модули является чисто логическим и может быть легко нарушено разработчиками. Может появиться модуль core, который постепенно превращается в помойку, может расти граф зависимостей между модулями и так далее. Для избежания таких проблем разработка должна вестить либо очень зрелой командой, либо под руководством архитектора, который на full time занимается code review и бьет нарушающих логическую структуру разработчиков по рукам.

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

Сервис-ориентированная архитектура


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

Сервис-ориентированная архитектура (SOA = service oriented architecture) решает все обозначенные проблемы монолита: при изменении затрагивается только одна служба, а четко определенный API поддерживает хорошую инкапсуляцию компонент.

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

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

Сервис-ориентированная архитектура неплохо поддерживается архитектурным коммьюнити и вендорами. Отсюда следует наличие множества курсов и сертификаций, хорошо проработанных паттернов. К последним относится, например, не безызвестная сервисная шина предприятия (ESB = enterprise service bus). При этом ESB это багаж от вендоров, она не обязательно должна использоваться в SOA.

Пик популярности сервис-ориентированной архитектуры приходился примерно на 2008 год, после чего она пошла на спад, который стал существенно более резким после появления микросервисов (~2015 год).

Заключение


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

Подробнее..

Паттерн сага как способ обеспечения консистентности данных

18.09.2020 14:13:41 | Автор: admin
Всем привет. Уже сейчас в OTUS открывает набор в новую группу курса Highload Architect. В связи с этим я продолжаю серию своих публикаций, написанных специально для этого курса, а также приглашаю вас на свой бесплатный демо урок по теме: Индексы в MySQL: best practices и подводные камни. Записаться на вебинар можно тут.



Введение


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

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

Паттерн Сага


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

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

Типов транзакций в саге несколько, целых четыре:

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

Организовывать сагу можно с помощью хореографии или оркестрации.

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

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

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

Сага позволяет добиться ACD-модели (Atomicity + Consistency + Durability в терминах ACID), но одну букву мы потеряли. Недостаток буквы I приводит к известным проблемам недостатка изолированности. К ним относятся: потерянные обновления (lost updates) одна сага перезаписывает изменения, внесенные другой, не читая их при этом, грязное чтение (dirty reads) транзакция или сага читают незавершенные обновления другой саги, нечеткое/неповторяемое чтение (fuzzy/nonrepeatable reads) два разных этапа саги читают одни и те же данные, но получают разные результаты, потому что другая сага внесла изменения. Существует ряд паттернов, позволяющих пофиксить те или иные аномалии: семантическая блокировка, коммутативные обновления, пессимистическое представление, повторное чтение значения, файл изменений и по значению. Вопрос обеспечения изоляции остается открытым.

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

Заключение


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

Подробнее..

Выбор архитектурного стиля (часть 3)

28.09.2020 02:15:18 | Автор: admin
Привет, Хабр. Сегодня я продолжаю серию публикаций, которую написал специально к старту нового потока курса Software Architect.



Введение


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

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

Сейчас мы наконец определим основные характеристики микросервисной архитектуры.

Отношение архитектур


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

Характеристики микросервисной архитектуры


Основными характеристиками микросервисной архитектуры являются:

Организация в соответствии с бизнес-возможностями
(Organized around Business Capabilities)
Продукты, а не проекты (Products not Projects)
Умные точки входа и глупые каналы (Smart endpoints and
dumb pipes)
Децентрализованное управление (Decentralized Governance)
Децентрализованное управление данными (Decentralized
Data Management)
Автоматизация инфраструктуры (Infrastructure Automation)
Страховка от сбоев (Design for failure)
Архитектура с эволюционным развитием (Evolutionary
Design)

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

Организация в соответствии с бизнес-возможностями (Organized around Business Capabilities)


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

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

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

Продукты, а не проекты (Products not Projects)


Проектный подход при котором команда передает разработанную функциональность другим командам в случае микросервисной архитектуры совершенно не подходит. Команда должна поддерживать систему на протяжении всего ее жизненного цикла. Компания Amazon, один из флагманов внедрения микросервисов, заявляла: вы создаете продукт, и вы же запускаете его (you build, you run it). Продуктовый подход позволяет команде почувствовать потребности бизнеса.

Умные точки входа и глупые каналы (Smart endpoints and dumb pipes)


SOA архитектура большое внимание уделяла каналам связи, в частности Enterprise Service Bus (сервисная шина предприятия). Что зачастую приводит к Erroneous Spaghetti Box, то есть сложность монолита переходит в сложность связей между сервисами. В микросевисной архитектуре используются только простые способы взаимодействия.

Децентрализованное управление (Decentralized Governance)


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

Децентрализованное управление данными (Decentralized Data Management)


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

Автоматизация инфраструктуры (Infrastructure Automation)


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

Страховка от сбоев (Design for failure)


Многочисленные сервисы MSA подвержены сбоям. При этом, обработка ошибок в распределенной системе весьма не тривиальная задача. Архитектура приложений должна быть устойчива к таким сбоям. Ребекка Парсонс считает очень важным, что мы больше не используем даже внутрипроцессное взаимодействие между сервисами, вместо этого для связи мы прибегаем к HTTP, который и близко не бывает столь же надёжен.

Архитектура с эволюционным развитием (Evolutionary Design)


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

Заключение


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



Читать часть 2
Подробнее..

Выбор архитектурного стиля. Часть 4

12.10.2020 16:11:28 | Автор: admin
В конце октября запускаем новую группу курса Архитектура и шаблоны проектирования и приглашаем всех специалистов на бесплатный Demo-урок Шаблон адаптер, который проведёт Матвей Калинин главный разработчик в одном из крупнейших банков страны.




Введение


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

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

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

Проблема выбора


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

Независимое развертывание


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

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

Улучшение отказоустойчивости


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

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

Гибкость и ясность


Жесткие физические границы сервиса обеспечивают низкую связанность, хорошо управляемые части, их независимую разработку. Разработчики хорошо понимают свою часть системы.

Но, независимость разработчиков приводит к эффекту колодца, когда никто не видит картины целиком.

Варьирование технологического стека


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

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

Техническая сложность


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

Распространенные заблуждения


С микросервисной архитектурой связан ряд заблуждений. Вот некоторые из них:
  1. Код будет чище. Код не будет чище, если не будут предприняты усилия, чтобы код стал чище.
  2. Писать модули, решающие одну задачу легче.
    Не легче, поскольку у таких модулей будет очень много интеграций.
  3. Это работает быстрее, чем монолит.
    Монолит работает быстрее из-за меньшего количества сетевых вызовов.
  4. Инженерам проще, если не нужно работать с единой кодовой
    базой.
  5. Это самый простой способ обеспечить автоматическое
    масштабирование.
  6. И тут где-то замешан Докер.


Заключение


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

image
Подробнее..

Окей Гугл, гайд по AIDL

23.01.2021 14:23:52 | Автор: admin

Вступление

Привет хабр!

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

О существовании AIDL я знал довольно давно, однако до этого момента с IPC в андройде не сталкивался, и этот опыт был для меня первым.

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

Итак, приступим.

Как это работает на примере калькулятора

В основе взаимодействия лежит bound-сервис, предоставляющий сторонним приложениям доступ к определенному набору методов. Другими словами, одно приложение выступает в роли "клиента" а другое в роли "сервера", причем общаются они по строго заданному интерфейсу, который идентичен для обоих приложений.

Чтобы разобраться с основами, давайте напишем простое приложение-калькулятор, а потом перейдем к более сложным примерам.

Структура проектаСтруктура проекта

Итак, имеем два модуля-приложения: :app-server и :app-client, из названия понятно кто за что отвечает.

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

Далее в приложении-сервере нам необходимо написать тот самый bound-сервис, через который и будет происходить общение между нашими приложениями. В единственном методе сервиса onBind пока-что будем возвращать null.

Теперь перейдем к самой работе с AIDL. В модуле :aidl в папке src/main/aidl/*пакет приложения* создадим файл Calculator.aidl, в котором напишем интерфейс имеющий всего один метод сложения чисел:

package com.example.aidl;// Calculator.aidlinterface Calculator {    int sum(int first, int second);}

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

Однако первое что вы заметите при написании AIDL-интерфейса, так это его плохую поддержку в Android Studio - забудьте про автодополнение кода, автоматическую подмену импортов и пакета при перемещении файла, да черт, там даже подсветка для JavaDoc-комментариев отсутствует! Очень разочаровало.

Во время сборки проекта у нас запустится gradle-таск, который сгенерирует java-интерфейс содержащий в себе логику IPC транзакций. О его внутренностях нам знать не обязательно, нас интересует абстрактный класс Calculator.Stub который является наследником android.os.IBinder, его-то мы и должны будем вернуть в методе onBind нашего bound-сервиса. Теперь сервис будет выглядеть следующим образом:

class BoundService : Service() {    override fun onBind(intent: Intent?): IBinder? {        return object : Calculator.Stub() {            override fun sum(first: Int, second: Int): Int {                return first + second // реализация метода            }        }    }}

Осталось только подключиться к нему на стороне клиента и вызвать метод sum(first, second).

Чтобы мы смогли подключиться к сервису он должен быть доступен сторонним приложениям, поэтому добавим ему в манифест интент-фильтр:

<service    android:name=".BoundService"    android:process=":remote"    android:exported="true">    <intent-filter>        <action android:name="com.example.aidl.REMOTE_CONNECTION" />    </intent-filter></service>

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

Также для подключения требуется отправлять явный (explicit) Intent, т.е он обязан содержать имя пакета и полный путь к классу сервиса, для этого напишем следующий метод:

Создание явного интента
private fun createExplicitIntent(): Intent {    val intent = Intent("com.example.aidl.REMOTE_CONNECTION")    val services = packageManager.queryIntentServices(intent, 0)    if (services.isEmpty()) {        throw IllegalStateException("Приложение-сервер не установлено")    }    return Intent(intent).apply {        val resolveInfo = services[0]        val packageName = resolveInfo.serviceInfo.packageName        val className = resolveInfo.serviceInfo.name        component = ComponentName(packageName, className)    }}

Также, начиная с Android 11 для подключения к сервису в клиентском приложении требуется указать в манифесте тег <queries> и прописать туда <action> сервиса, к которому мы будем подключаться.

Queries tag
<queries>    <intent>        <action android:name="com.example.aidl.REMOTE_CONNECTION" />    </intent></queries>

Ну и последнее что осталось сделать - реализовать интерфейс ServiceConnection, в котором полученный объект типа android.os.IBinder мы приведём к нашему типу com.example.aidl.Calculator:

private var calculator: Calculator? = nullprivate val serviceConnection = object : ServiceConnection {    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {        calculator = Calculator.Stub.asInterface(service)    }    override fun onServiceDisconnected(name: ComponentName?) {        calculator = null    }}override fun onStart() {    super.onStart()    bindService(createExplicitIntent(), serviceConnection, Context.BIND_AUTO_CREATE)}override fun onStop() {    super.onStop()    unbindService(serviceConnection)}

Всё! Теперь у нас есть объект calculator, методы которого выполняются в другом приложении. Вызвав метод calculator.sum(2, 2) на выходе получим 4.

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

Кейс по-сложнее 1 - модель данных

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

В AIDL поддерживаются следующие типы данных:
Все примитивы из Java (int, long, char, boolean и т.д)
Массивы примитивов (int[] и т.д.)
Строковые типы CharSequence и String
List<T> содержащий данные типа из этого списка
Map<*, *> содержащий данные типа из этого списка, но без параметризации! Т.е не получится написать Map<String, Integer> - компилятор выдаст ошибку
Parcelable классы, в том числе Bundle
Другие AIDL-интерфейсы (рассмотрим чуть позже)

Поддержка Parcelable меня очень порадовала, ведь можно использовать обычные data классы, помечать их аннотацией @Parcelize и сразу же иметь возможность передавать между процессами, это ли не чудо?!

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

@Parcelizedata class Sum(val sum: Int) : Parcelable
Описание Parcelable прямо в AIDL

В официальной документации сказано, что начиная с Android 10 появилась возможность описания Parcelable классов напрямую в AIDL, однако по какой-то мистической причине у меня это не работает.

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

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

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

Теперь наш калькулятор будет выглядеть следующим образом:

package com.example.aidl;import com.example.aidl.Sum;// Calculator.aidlinterface Calculator {    Sum sum(int first, int second);}

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

Кейс по-сложнее 2 - асинхронные вызовы

Если выполнение метода sum(first, second) будет занимать много времени, в конечном счете мы получим зависания на UI треде, а что ещё хуже - ANR. Для избежания такой ситуации можно вынести выполнение метода на другой поток, а результат выполнения возвращать через Callback.

Напишем AsyncCallback.aidl в котором будем получать результат Sum:

package com.example.aidl;import com.example.aidl.Sum;// AsyncCallback.aidlinterface AsyncCallback {    void onSuccess(in Sum sum);}
Про ключевые слова in/out/inout

Ключевые слова in/out/inout позволяют Binder'у пропустить определенный этап маршаллинга данных, повышая производительность IPC транзакций.

Рассмотрим следующий пример - у нас есть метод требующий на вход массив строк помеченный ключевым словом in:

// AidlExample.aidlinterface AidlExample {    void fillArray(in String[] array);}

На стороне клиента мы заполняем этот массив какими-либо данными, например "Cat" и "Dog", а на стороне сервера изменяем значения этих строк на "Apple" и "Strawberry".

Изменится-ли массив на стороне клиента после манипуляций на стороне сервера? Нет, не изменится, потому что in как бы "ограничивает" доступ к оригинальному массиву. Однако на стороне сервера массив будет изменён.

Как вы наверное уже догадались, чтобы массив обновился на стороне клиента мы должны пометить его ключевым словом inout - таким образом на стороне сервера будет доступ как на чтение массива, так и на запись.

Ещё есть ключевое слово out, его отличие от inout в том что массив на стороне клиента будет заполнен теми значениями, которые были установлены на стороне сервера. Пример - на стороне клиента передаем массив из "Cat" и "Dog", а на стороне сервера с массивом ничего не делаем. После выполнения метода оригинальный массив будет содержать null, потому что приложение-сервер его не заполнило.

Учтите что все примитивы в AIDL по-умолчанию помечены словом in, и вы не можете использовать для них свойство out или inout. Но если у вас всё же возникла такая необходимость, рассмотрите вариант использования списка или массива.

Теперь обновим основной класс калькулятора c использованием нового коллбека:

package com.example.aidl;import com.example.aidl.AsyncCallback;// Calculator.aidlinterface Calculator {    void sum(int first, int second, AsyncCallback callback);}

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

Далее останется лишь запустить в сервисе асинхронную операцию и вернуть результат в callback.onSuccess(sum), на этом всё.

Ключевое слово oneway

В предыдущем примере мы запускали задачу в отдельном потоке внутри bound-сервиса, но в AIDL также предусмотрена возможность выполнения самого метода sum(first, second) в другом потоке, для этого его нужно пометить ключевым словом oneway:

package com.example.aidl;import com.example.aidl.AsyncCallback;// Calculator.aidlinterface Calculator {    // P.S слово oneway применимо только к void-методам    oneway void sum(int first, int second, AsyncCallback callback);}

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

Ещё тут присутствует один неочевидный момент - если клиент и сервер находятся в одном процессе, то такой метод будет выполнен синхронно.

Кейс с закрытием приложения

Официальная документация советует нам всегда отлавливать ошибки типа android.os.RemoteException которые могут возникать при потере соединения.

Если во время асинхронной операции клиентское приложение будет закрыто, то при вызове callback.onSuccess(sum) на стороне сервера мы получим ошибку android.os.DeadObjectException.

Лечится это банально оберткой в try-catch:

// BoundService.ktoverride fun sum(first: Int, second: Int, callback: AsyncCallback?) {    try {        val sum = Sum(first + second)        Thread.sleep(2000) // делаем что-то тяжелое        callback?.onSuccess(sum)    } catch (e: RemoteException) {        Log.e("BoundService", e.message, e)    }}

Тоже самое работает и в обратную сторону - если на момент вызова приложение-сервер недоступно, то на стороне клиента получим android.os.DeadObjectException.

Кейс с обновлением приложения

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

Исправляется очень просто - в момент подключения к сервису регистрируем BroadcastReceiver и слушаем событие ACTION_PACKAGE_REPLACED, а в момент его получения переподключаемся к сервису:

private val appUpdateReceiver = object : BroadcastReceiver() {    override fun onReceive(context: Context?, intent: Intent?) {        if (intent?.data?.schemeSpecificPart == "com.example.appserver") {            unregisterReceiver(this)            reconnect()        }    }}private fun reconnect() {    bindService(createExplicitIntent(), serviceConnection, Context.BIND_AUTO_CREATE)    registerReceiver(appUpdateReceiver, IntentFilter().apply {        addAction(Intent.ACTION_PACKAGE_REPLACED)        addDataScheme("package")    })}

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

Кейс по-сложнее 3 - ошибки

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

Добавим в наш коллбек новый метод onError, который будет вызван если на стороне сервера произойдёт ошибка:

package com.example.aidl;import com.example.aidl.Sum;import java.lang.RuntimeException;// AsyncCallback.aidlinterface AsyncCallback {    void onSuccess(in Sum sum);    void onError(in RuntimeException exception);}

Ну вот мы и встретили первую боль - мы не можем передавать ошибки т.к они не реализуют интерфейс Parcelable.

Первое решение которое приходит в голову - создать нечто вроде Parcelable-контейнера для ошибок, т.к всё-таки у нас есть возможность записать Serializable объект в Parcel.

@Parcelizeclass ExceptionHolder(val exception: RuntimeException): Parcelable

Теперь наш коллбек будет выглядеть примерно так:

package com.example.aidl;import com.example.aidl.ExceptionHolder;import com.example.aidl.Sum;// AsyncCallback.aidlinterface AsyncCallback {    void onSuccess(in Sum sum);    void onError(in ExceptionHolder exception);}

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

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

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

Таким образом, текущая реализация всего этого будет выглядеть примерно так:

Много кода

Класс, который будет использоваться для передачи ошибки и "воссоздании" её на стороне клиента:

@Parcelizeclass AidlException(    private val errorMessage: String?,    private val errorCode: Int = RUNTIME_EXCEPTION) : Parcelable {    companion object {        const val RUNTIME_EXCEPTION = 1000        const val ARITHMETIC_EXCEPTION = 1001        // TODO другие коды ошибок...    }    // конвертация на стороне клиента    fun toException(): Exception {        return when (errorCode) {            RUNTIME_EXCEPTION -> RuntimeException(errorMessage)            ARITHMETIC_EXCEPTION -> ArithmeticException(errorMessage)            else -> RuntimeException(errorMessage)        }    }}

Коллбек, ответственный за передачу результата между процессами:

package com.example.aidl;import com.example.aidl.AidlException;import com.example.aidl.Sum;// AsyncCallback.aidlinterface AsyncCallback {    void onSuccess(in Sum sum);    void onError(in AidlException aidlException);}

Отправка ошибки на стороне сервиса:

override fun sum(first: Int, second: Int, callback: AsyncCallback?) {    try {        val sum = Sum(first + second)        Thread.sleep(2000) // делаем что-то тяжелое        throw ArithmeticException("Unable to calculate the result") // не получилось :(        callback?.onSuccess(sum)    } catch (e: Throwable) {        Log.e("BoundService", e.message, e)        if (e is ArithmeticException) {            val aidlException = AidlException(e.message, AidlException.ARITHMETIC_EXCEPTION)            callback?.onError(aidlException)        }    }}

Получение и конвертация ошибки на стороне клиента:

calculator?.sum(2, 2, object : AsyncCallback.Stub() {    override fun onSuccess(sum: Sum?) {        Toast.makeText(this@MainActivity, sum.toString(), Toast.LENGTH_SHORT).show()    }    override fun onError(aidlException: AidlException?) {        val exception = aidlException?.toException()        Toast.makeText(this@MainActivity, exception?.message, Toast.LENGTH_SHORT).show()    }})

Кейс по-сложнее 4 - дженерики

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

Если вы думали что весь ужас закончится на передаче ошибок, вы ошибались - в AIDL нет дженериков. Формально они, конечно, есть - мы можем указать тип списка вроде List<String>, но указать тип у собственных Parcelable классов не получится.

Ладно подумал я, пускай их не будет - исходники обоих приложений прямо передо мной, и я точно знаю какой тип данных ожидать на "той" стороне, поэтому мне не составит труда просто передать такой объект из одного приложения в другое. Таким образом можно будет избавится от дублирования .aidl файлов для каждого Parcelable класса.

Давайте напишем такой контейнер и опробуем нашу "технику":

@Parcelizedata class AidlResult<T : Parcelable>(val data: T) : Parcelable

Коллбек с результатом теперь присылает AidlResult вместо Sum:

package com.example.aidl;// опять импорты вручную менять! >:(import com.example.aidl.AidlException;import com.example.aidl.AidlResult;// AsyncCallback.aidlinterface AsyncCallback {    void onSuccess(in AidlResult aidlResult);    void onError(in AidlException aidlException);}

На стороне сервера Sum оборачивается в AidlResult:

override fun sum(first: Int, second: Int, callback: AsyncCallback?) {    try {        val sum = Sum(first + second)        val aidlResult = AidlResult(sum)        Thread.sleep(2000) // делаем что-то тяжелое        callback?.onSuccess(aidlResult)    } catch (e: Throwable) {        // ...    }}

А на стороне клиента приводим data из AidlResult к типу Sum:

calculator?.sum(2, 2, object : AsyncCallback.Stub() {    override fun onSuccess(aidlResult: AidlResult<*>?) {        val sum = aidlResult?.data as? Sum        Toast.makeText(this@MainActivity, sum.toString(), Toast.LENGTH_SHORT).show()    }    override fun onError(aidlException: AidlException?) {        val exception = aidlException?.toException()        Toast.makeText(this@MainActivity, exception?.message, Toast.LENGTH_SHORT).show()    }})

Как думайте, заработает-ли оно с первого раза? Ответ будет отличаться в зависимости от версии kotlin-parcelize плагина.

Когда я только начинал изучать AIDL, в плагине kotlin-android-extensions был баг с типизацией для дженерик-классов который не позволял использовать их в AIDL, т.е аннотация @Parcelize генерировала неподдерживаемый для AIDL код.

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

Единственным решением которое было мной найдено, это использовать вот такой костыль:

Костыль для дженериков
/** * https://youtrack.jetbrains.com/issue/KT-25807 */data class AidlResult<T : Parcelable>(    private var data: T?,    private var classType: Class<T>?) : Parcelable {    @Suppress("UNCHECKED_CAST")    constructor(parcel: Parcel) : this(null, null) {        classType = parcel.readSerializable() as? Class<T>        data = parcel.readParcelable(classType?.classLoader) as? T    }    override fun writeToParcel(parcel: Parcel, flags: Int) {        parcel.writeSerializable(classType)        parcel.writeParcelable(data, flags)    }    override fun describeContents(): Int = 0    companion object CREATOR : Parcelable.Creator<AidlResult<out Parcelable>> {        override fun createFromParcel(parcel: Parcel): AidlResult<out Parcelable> {            return AidlResult(parcel)        }        override fun newArray(size: Int): Array<AidlResult<out Parcelable>?> {            return arrayOfNulls(size)        }    }}

Т.е по сути мы динамически загружаем класс используя ClassLoader, да ещё и лишаемся возможности сгенерировать шаблонный код через @Parcelize.

Я не знаю кого за это благодарить Google или JetBrains, но с переходом на версии Kotlin 1.4.10 -> 1.4.21 и с плагина kotlin-android-extensions на kotlin-parcelize всё заработало и эти костыли не нужны - респект!

Заключение

Работа с IPC в андройде не идеальна - очень расстроила плохая поддержка Android Studio, отсутствие типизации в интерфейсах и механизма для передачи ошибок, но всё-же это был интересный опыт. Надеюсь что статья помогла вам разобраться с работой в AIDL и сэкономит немного времени на разработку приложения.

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

Спасибо!

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru