Что такое domain realm

Что такое domain realm. Смотреть фото Что такое domain realm. Смотреть картинку Что такое domain realm. Картинка про Что такое domain realm. Фото Что такое domain realmПодключаемся к Active Directory при помощи realmd

Архив номеров / 2015 / Выпуск №1-2 (146-147) / Подключаемся к Active Directory при помощи realmd

Что такое domain realm. Смотреть фото Что такое domain realm. Смотреть картинку Что такое domain realm. Картинка про Что такое domain realm. Фото Что такое domain realmСЕРГЕЙ ЯРЕМЧУК, автор более 800 статей и шести книг. С «СА» с первого номера. Интересы: сетевые технологии, защита информации, свободные ОС, grinder@samag.ru

Подключаемся к Active Directory с помощью realmd

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

Что у нас есть сегодня? Microsoft для Windows Server 2008 R2 – 2012 R2 предлагает Identity Management for UNIX (Служба управления идентификацией UNIX), являющийся частью роли контроллера домена и позволяющий использовать для входа в UNIX учетные записи Active Directory. Но сейчас он уже объявлен как устаревший. Большие возможности дает Configuration Manager, хотя, соответственно, требует немалых вложений.

Приверженцы Unix ориентируются на связку Samba (winbind) и Kerberos или на решения сторонних компаний (PowerBroker Identity Services, Centrify Server Suite, Quest Authentication Services).

Первый бесплатен, рекомендуется многими специалистами как более правильный, но требует тщательных настроек. Вторые вмешиваются в схему AD и требуют дополнительного лицензирования (PowerBroker имеет и Open Source-версию).

Realmd (Realm Discovery) [1] – сервис D-Bus, позволяющий производить настройку сетевой аутентификации и членства в домене (AD или IPA/FreeIPA) без сложных настроек. Информация о домене обнаруживается автоматически. Дляаутентификации и проверки учетных записей realmd использует SSSD (через Kerberos и LDAP) [2] или Winbind. Версия 0.1 была представлена в конце июля 2012 года, на момент написания этих строк актуальной была 0.15.2. В настоящее время realmd интегрирован в kickstart, Центр управления GNOME и другие инструменты.

Покажем подключение к домену example.org с помощью realmd на примере Ubuntu Linux 14.04 LTS, в других дистрибутивах все показанное будет отличаться только особенностями работы менеджера пакетов.

Перед началом настроек необходимо настроить DNS, лучше всего, когда в /etc/resolv.conf прописан сам контролер домена. Все, что необходимо, уже есть в репозитории:

$ sudo apt-get install realmd samba-common-bin samba-libs sssd-tools krb5-user adcli

При конфигурировании krb5-user будет запрошена область по умолчанию, вводим имя домена в верхнем регистре – EXAMPLE.ORG.

Что такое domain realm. Смотреть фото Что такое domain realm. Смотреть картинку Что такое domain realm. Картинка про Что такое domain realm. Фото Что такое domain realm

Рисунок 1. Конфигурирование пакета krb5-user

Конфигурационный файл /etc/realmd.conf не создается, действуют параметры по умолчанию. В результате может возникнуть конфликт имен, что приведет к ошибке при подключении. Чтобы избежать проблем, следует его создать такого содержания:

$ sudo nano /etc/realmd.conf

Файл имеет немало полезных опций, но пока этого достаточно.

Сервисы SSSD (System Security Services Daemon) также требуют настройки. Добавим фильтр учетных записей и количество попыток подключений:

$ sudo nano /etc/sssd/sssd.conf

В некоторых ситуациях следует добавить:

Указываем права доступа:

$ sudo chmod 0600 /etc/sssd/sssd.conf

Пробуем найти доступные домены:

$ sudo realm discover

$ sudo realm discover EXAMPLE.ORG

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

Using default cache: /tmp/krb5cc_1000

Using principal: Administrator@EXAMPLE.ORG

Password for Administrator@EXAMPLE.ORG:

Authenticated to Kerberos v5

Подключаемся (см. рис. 2).

Что такое domain realm. Смотреть фото Что такое domain realm. Смотреть картинку Что такое domain realm. Картинка про Что такое domain realm. Фото Что такое domain realm

Рисунок 2. Подключаемся к Active Directory

Для запуска понадобятся права root, иначе получим сообщение:

realm: Couldn’t join realm: Not authorized to perform this action

Теперь, если ввести

получим полную информацию о домене (см. рис. 3), а в консоли Active Directory Users and Computers появится новый компьютер (см. рис. 4).

Что такое domain realm. Смотреть фото Что такое domain realm. Смотреть картинку Что такое domain realm. Картинка про Что такое domain realm. Фото Что такое domain realm

Рисунок 3. Информация о домене

Что такое domain realm. Смотреть фото Что такое domain realm. Смотреть картинку Что такое domain realm. Картинка про Что такое domain realm. Фото Что такое domain realm

Рисунок 4. Вкладка Active Directory Users and Computers

Некоторые действия и информацию можно получить с помощью утилиты adsli.

$ adcli info example.org

domain-controller-flags = pdc gc ldap ds kdc timeserv closest writable good-timeserv full-secret ads-web

Также стоит добавить новое правило в /etc/sudoers, чтобы доменный пользователь мог получать права администратора локального компьютера:

$ sudo nano /etc/sudoers

%domain\ admins@example.com ALL=(ALL) ALL

Перегружаем систему и регистрируемся как доменный пользователь «пользователь@домен».

Источник

Realm Names

Applies to: Windows Server 2022, Windows Server 2019, Windows Server 2016

You can use this topic for an overview of using realm names in Network Policy Server connection request processing.

The User-Name RADIUS attribute is a character string that typically contains a user account location and a user account name. The user account location is also called the realm or realm name, and is synonymous with the concept of domain, including DNS domains, Active DirectoryВ® domains, and Windows NT 4.0 domains. For example, if a user account is located in the user accounts database for a domain named example.com, then example.com is the realm name.

In another example, if the User-Name RADIUS attribute contains the user name user1@example.com, user1 is the user account name and example.com is the realm name. Realm names can be presented in the user name as a prefix or as a suffix:

Example\user1. In this example, the realm name Example is a prefix; and it is also the name of an Active DirectoryВ® Domain Services (AD DS) domain.

user1@example.com. In this example, the realm name example.com is a suffix; and it is either a DNS domain name or the name of an AD DS domain.

You can use realm names configured in connection request policies while designing and deploying your RADIUS infrastructure to ensure that connection requests are routed from RADIUS clients, also called network access servers, to RADIUS servers that can authenticate and authorize the connection request.

When NPS is configured as a RADIUS server with the default connection request policy, NPS processes connection requests for the domain in which the NPS is a member and for trusted domains.

To configure NPS to act as a RADIUS proxy and forward connection requests to untrusted domains, you must create a new connection request policy. In the new connection request policy, you must configure the User Name attribute with the realm name that will be contained in the User-Name attribute of connection requests that you want to forward. You must also configure the connection request policy with a remote RADIUS server group. The connection request policy allows NPS to calculate which connection requests to forward to the remote RADIUS server group based on the realm portion of the User-Name attribute.

Acquiring the realm name

The realm name portion of the user name is provided when the user types password-based credentials during a connection attempt or when a Connection Manager (CM) profile on the user’s computer is configured to provide the realm name automatically.

You can designate that users of your network provide their realm name when typing their credentials during network connection attempts.

For example, you can require users to type their user name, including the user account name and the realm name, in User name in the Connect dialog box when making a dial-up or virtual private network (VPN) connection.

In addition, if you create a custom dialing package with the Connection Manager Administration Kit (CMAK), you can assist users by adding the realm name automatically to the user account name in CM profiles that are installed on users’ computers. For example, you can specify a realm name and user name syntax in the CM profile so that the user only has to specify the user account name when typing credentials. In this circumstance, the user does not need to know or remember the domain where their user account is located.

During the authentication process, after users type their password-based credentials, the user name is passed from the access client to the network access server. The network access server constructs a connection request and includes the realm name within the User-Name RADIUS attribute in the Access-Request message that is sent to the RADIUS proxy or server.

If the RADIUS server is an NPS, the Access-Request message is evaluated against the set of configured connection request policies. Conditions on the connection request policy can include the specification of the contents of the User-Name attribute.

You can configure a set of connection request policies that are specific to the realm name within the User-Name attribute of incoming messages. This allows you to create routing rules that forward RADIUS messages with a specific realm name to a specific set of RADIUS servers when NPS is used as a RADIUS proxy.

Attribute manipulation rules

Before the RADIUS message is either processed locally (when NPS is being used as a RADIUS server) or forwarded to another RADIUS server (when NPS is being used as a RADIUS proxy), the User-Name attribute in the message can be modified by attribute manipulation rules. You can configure attribute manipulation rules for the User-Name attribute by selecting User name on the Conditions tab in the properties of a connection request policy. NPS attribute manipulation rules use regular expression syntax.

You can configure attribute manipulation rules for the User-Name attribute to change the following:

Remove the realm name from the user name (also known as realm stripping). For example, the user name user1@example.com is changed to user1.

Change the realm name but not its syntax. For example, the user name user1@example.com is changed to user1@wcoast.example.com.

Change the syntax of the realm name. For example, the user name example\user1 is changed to user1@example.com.

After the User-Name attribute is modified according to the attribute manipulation rules that you configure, additional settings of the first matching connection request policy are used to determine whether:

The NPS processes the Access-Request message locally (when NPS is being used as a RADIUS server).

The NPS forwards the message to another RADIUS server (when NPS is being used as a RADIUS proxy).

Configuring the NPS-supplied domain name

When the user name does not contain a domain name, NPS supplies one. By default, the NPS-supplied domain name is the domain of which the NPS is a member. You can specify the NPS-supplied domain name through the following registry setting:

Incorrectly editing the registry can severely damage your system. Before making changes to the registry, you should back up any valued data on the computer.

Some non-Microsoft network access servers delete or modify the domain name as specified by the user. As the result, the network access request is authenticated against the default domain, which might not be the domain for the user’s account. To resolve this problem, configure your RADIUS servers to change the user name into the correct format with the accurate domain name.

Источник

Идентифиакция пользователей в SIP

Протокол SIP предоставляет статусонезависимый, основанной на технологии двухстороннего обмена, механизм для авторизации, который используется в протоколе HTTP. В любое время, когда прокси сервер или клиент (UA) принимает запрос (за исключением тех, что описаны в Разделе 22.1), он МОЖЕТ запросить у инициатора этого запроса гарантию подлинности его идентификации. Когда инициатор уже был идентифицирован, сторона, принимающая запрос, ДОЛЖНА убедиться может или нет, для этого авторизированного пользователя, быть выполнены действия указанные в запросе. В этом документе нет рекомендаций или обсуждений самих систем для авторизации.

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

Обратите внимание, что из-за слабой защищенности, использование «Базовой» (Basic) схемы авторизации (где логин и пароль передаются по каналам связи незашифрованными) объявлено нежелательным. Все Сервера НЕ ДОЛЖНЫ подтверждать полномочия клиента, если используется «базовая» схема авторизации и НЕ ДОЛЖНЫ как-либо реагировать на запросы, которые используют эту схему авторизации. Эти изменения описаны в RFC 2543.

Что такое «realm»?

Какая авторизация должна использоваться: WWW или proxy-auth?

Если сервер, проверяющий авторизацию, не может принять авторизационные данные, переданные в запросе, то он ДОЛЖЕН ответить сообщением с кодом 401 (Unauthorized). Ответ ДОЛЖЕН включать в себя заголовочное поле: «WWW-Authenticate», содержащее, как минимум одну (и по возможности, заново сгенерированную) строку для проведения авторизации, пригодную к использованию запрашивающим клиентом. Если прокси сервер, проверяющий авторизацию, не может принять авторизационные данные, переданные в запросе, то он ДОЛЖЕН ответить сообщением с кодом 407 (Proxy Authentication Required). Ответ ДОЛЖЕН включать в себя заголовочное поле: «Proxy-Authenticate», содержащее, как минимум одну (и по возможности, заново сгенерированную) строку для проведения авторизации на прокси сервере, пригодную к использованию запрашивающим клиентом.

Авторизация для сообщений ACK и CANCEL

Digest авторизация в протоколе HTTP

Алгоритм работы HTTP Digest авторизации описан в документе: RFC2617 и расширен в документе RFC 3310.

Источник

Ввод компьютера в домен Windows

Содержание

Введение

Зачастую возникает необходимость ввести Linux-машину в существующий домен Windows. Например, чтобы сделать файловый сервер с помощью Samba. Сделать это очень просто, для этого вам понадобятся клиент Kerberos, Samba и Winbind.

Перед установкой желательно обновиться:

Установить всё это добро можно командой:

Также может понадобиться установить следующие библиотеки:

Либо, если вы используете Ubuntu Desktop, те же пакеты можно поставить через менеджер пакетов Synaptic.

Настройка DNS

Если у вас статический IP-адрес, то в Ubuntu Desktop это можно сделать через Network Manager, в Ubuntu Server необходимо изменить содержимое файла /etc/resolv.conf на примерно такое:

В современных дистрибутивах файл resolv.conf создается автоматически и править вручную его не нужно. Для получение нужного результата нужно добавить необходимые изменения в файл: /etc/resolvconf/resolv.conf.d/head Данные которые будут добавлены в него, будут автоматически вставлены в файл /etc/resolv.conf

Чтобы добавить еще один nameserver нужно убрать комментарий перед prepend domain-name-servers и указать ip сервера:

Для применения изменений остается перезапустить службу:

Теперь убедитесь, что вы задали нужное имя компьютера в файле /etc/hostname :

Кроме того необходимо отредактировать файл /etc/hosts так, чтобы в нём была запись с полным доменным именем компьютера и обязательно коротким именем хоста, ссылающаяся на один из внутренних IP:

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

Настройка синхронизации времени

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

Если в сети существует сервер точного времени, то можно воспользоваться им или любым публичным:

После чего перезапустите демон ntpd :

Теперь пора настраивать непосредственно взаимодействие с доменом.

Настройка авторизации через Kerberos

Это не все возможные опции настройки Kerberos, только основные. Однако их обычно достаточно.

Теперь настало время проверить, что мы можем авторизоваться в домене. Для этого выполните команду

Вместо username естественно стоит вписать имя существующего пользователя домена.

Убедиться в том, что билет получен, можно выполнив команду

Удалить все билеты (они вам вообще говоря не нужны) можно командой

Распространённые ошибки kinit

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

Вы ввели неверный пароль.

Указанного пользователя не существует в домене.

Настройка Samba и вход в домен

После того, как вы отредактируете smb.conf выполните команду

Она проверит вашу конфигурацию на ошибки и выдаст суммарную сводку о нём:

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

И в случае успеха вы увидите что-то похожее на:

Используемые параметры команды net

-U username%password : Обязательный параметр, вместо username необходимо подставить имя пользователя с правами администратора домена, и указать пароль.

Так же можно набрать команду:

Если все хорошо, можно увидеть:

Но иногда после сообщения о присоединении к домену выдаётся ошибка наподобие 3) :

Если всё прошло без ошибок, то поздравляем, вы успешно вошли в домен! Можете заглянуть в AD и убедиться в этом. Кроме того хорошо бы проверить, что вы можете видеть ресурсы в домене. Для этого установите smbclient :

Теперь можно просматривать ресурсы компьютеров домена. Но для этого нужно иметь билет kerberos, т.е. если мы их удалили, то получаем опять через kinit (см. выше). Посмотрим какие ресурсы предоставлены в сеть компьютером workstation :

Вы должны увидеть список общих ресурсов на этом компьютере.

Настройка Winbind

Winbind позволяет спроецировать всех пользователей и все группы AD в вашу Linux систему, присвоив им ID из заданного диапазона. Таким образом вы сможете назначать пользователей домена владельцами папок и файлов на вашем компьютере и выполнять любые другие операции, завязанные на пользователей и группы.

в новых версиях Samba уже устарели и при проверке конфига самбы с помощью testparm будет выдваться предупреждение:

WARNING: The «idmap uid» option is deprecated

WARNING: The «idmap gid» option is deprecated

Чтобы убрать предупреждения нужно заменить эти строки на новые:

idmap config * : range = 10000-20000

idmap config * : backend = tdb

Теперь перезапустите демон Winbind и Samba в следующем порядке:

Смотрим есть ли ошибки или предупреждения, если появится:

«rlimit_max: rlimit_max (1024) below minimum Windows limit (16384)»

Без перезагрузки можно устранить так:

Для сохранения после перезагрузки отредактировать файл /etc/security/limits.conf

После перезапуска проверьте, что Winbind установил доверительные отношения с AD командой:

А так же, что Winbind увидел пользователей и группы из AD командами 4) :

Итак, Winbind работает, однако в систему он ещё не интегрирован.

Добавление Winbind в качестве источника пользователей и групп

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

Для этого измените две строчки в файле /etc/nsswitch.conf :

добавив к ним в конец winbind :

также рекомендую привести строку files в файле /etc/nsswitch.conf к виду:

Теперь проверьте, что Ubuntu запрашивает у Winbind информацию о пользователях и группах, выполнив

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

Авторизация в Ubuntu через пользователей домена

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

Он-лайн авторизация

Для Ubuntu 13.10 чтобы появилось поле ручного ввода логина необходимо в любой файл из папки /etc/lightdm/lightdm.conf/ снизу добавить строку:

И, наконец, необходимо перенести запуск Winbind при загрузке системы после всех остальных служб (по умолчанию он запускается с индексом 20). Для этого в терминале выполните следующую команду:

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

Источник

Быстрый, простой, сложный: как мы выпилили Realm

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

Мы тоже использовали Realm — 3 года подряд. Сначала он помогал, потом раздражал, пару раз выстрелил и в конце чуть не вогнал команду в депрессию. В итоге мы удалили Realm из проекта, потому что это сложный инструмент, который нужно правильно обслуживать, а простота интеграции обманчива.

Примечание. Realm читается как «рэлм», не «реалм»

Зачем нужна база данных для заказа пиццы?

Кратко — незачем. База данных сначала прикрывала плохое API.

В 2017 году Dodo Pizza решила написать свое приложение. Серверная часть уже работала 6 лет и обслуживала 250+ пиццерий (на начало 2021 почти 700). Много работы было сделано для бизнеса, а для клиентов был только сайт — нужно делать приложение.

Чтобы подключить приложение — нужно новое API и срочно, ребята торопились. Чтобы ускориться, часть работы разделили: API было больше похоже на прокси для базы данных, а часть логики решало приложение.

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

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

Realm vs Core Data

Сложно вспомнить почему выбрали Realm, а не Core Data. Скорее всего, так было проще: схему базы рисовать не нужно, объекты создаются сразу в коде, работает быстрее, да и опыт работы с ней был. Так и поехало.

Как работало

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

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

получили данные из сети;

положили в базу, разметили связи между таблицами;

прочитали из базы, связанные объекты подтянулись сами;

переложили данные во view-модели, а дальше уже MVVM.

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

Недостатки Realm

После релиза проект перешел на поддержку от аутсорса к внутренней команде, её для этого только собрали. Новым разработчикам, конечно, не нравятся все старые решения, но понимания проекта не было, поэтому особо ничего не меняли — рефакторили только рядом с бизнес-фичами. С Realm работать не нравилось, но это был лишь вопрос вкуса.

Realm накладывает ряд ограничений сам по себе.

Хранит только сырые данные. Enum надо перекладывать в String или Int, Optional в RealmOptional, массивы в List, обратные ссылки в LinkedList. Чтобы превращать это в нормальные объекты надо писать какие-то конвертеры. В итоге кода становится сильно больше, модели дублируются, проект становится хрупче.

По всему коду размазано обращение к Realm: он импортируется в файл, передается в качестве параметра, из базы тянутся объекты. Мы активно заворачивали всё в репозитории, чтобы скрыть работу с базой, а интерфейсом выходил доменный объект. Но это дополнительный код и слой в архитектуру.

Работа с базой превратилась в целый слой, который надо поддерживать: писать маперы, обертки. Добавить новую сущность — это слишком много ручной работы: создать Entity, переложить из DTO в нее, потом из Entity в доменную модель. Это всё ещё и протестировать надо, а мы даже на UI выводить ничего не начали.

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

Realm — большая и очень тяжелая зависимость. Наш проект весил 55 Мб, Realm занимал 7 — и очень долго билдился. Мы решили проблему пребилдом — перенесли билд на этап pod install, стало реже и легче. Но плагин компиляции стал влиять и на другие поды, например, он не работал с XCFramework и мы не могли обновить поды, которые перешли на него. Убрать пребилд мы уже не могли, потому что привыкли к нормальной скорости сборки.

Ну и Realm мог бы и складывать свои файлы в одну папку!

Что такое domain realm. Смотреть фото Что такое domain realm. Смотреть картинку Что такое domain realm. Картинка про Что такое domain realm. Фото Что такое domain realmПо умолчанию Realm складывает всё в папку Documents

Проблемы в проекте из-за недостатков

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

Realm стал целым слоем обработки данных, все операции проходили через него. При этом, вся архитектура с бекапом в Realm не работает, если на девайсе мало места. Из-за размера фреймворка не получится переиспользовать код и написать, например, аппклипс: из 10 доступных мегабайт он займет все 10.

Страдает производительность. Обратные связи могут порождать очень большие и сложные деревья, сохранение и запись могут растягиваться. Мы столкнулись с этим в меню, когда появились изменяемые комбо. В комбо были слоты, каждый мог содержать десятки ссылок на продукты. При получении меню запись и чтение из базы занимало 2/3 времени: сетевой запрос проходил за полсекунды, а ещё одну мы просто разбирались с базой в приложении.

Офлайн-режим нашему приложению совсем не нужен, а персистентость нужна лишь частичная. Нет смысла хранить всю корзину, если можно обновить её с сервера по ID. Даже если захотим хранить её, то это проще сделать другим способом: на уровне кеша сети, ручным кешем оригинального JSON или конвертацией доменной модели в файл через Codable.

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

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

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

При обновлении Xcode каждый раз ломался CI. Обновление Realm его быстро чинило, но это лишние нервы каждый год.

Всё вместе это приводило к тому, что весь код вокруг Realm превращался в легаси:

его сложно рефакторить;

надо помнить про миграции;

могли быть неожиданные ошибки.

Это всё неприятно, но не критично: чуть больше кода, чуть меньше контроля, но работает.

Реально бесил лишь перформанс меню, но это можно было решить, стоило только сфокусироваться и попрофилировать. Коллеги на Android столкнулись с отсутствием каскадного удаления, но на iOS мы достаточно хорошо обработали это вручную, когда перед добавлением удаляли все прошлые объекты одного типа. Это же спасало и от разбухания базы.

Многие проблемы можно было решить инфраструктурно, но это только усложняло код. Например, чтобы не работать с объектами базы напрямую мы завели репозитории, которые конвертировали Realm Object в доменные объекты. Но это всё дополнительный код и усложнение.

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

Почему решили удалить — две «последние капли»

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

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

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

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

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

Ситуация всех вымотала, но мы чувствовали, что проблема была в нашем коде. Мы думали, что виноват не Realm, поэтому снова ничего не делали, но затаили обиду.

Realm мигрирует без учета версии схемы. Могут быть сложности при повторном переименовании Property.

Откат. Через два месяца мы столкнулись в непонятным крешем «Realm accessed in incorrect thread». Это было очень странно, потому что мы были точно уверены, что работаем с потоком правильно: вся работа с базой велась строго в отдельном потоке. Креш случался в самых разных местах, стабильности не было. Искали его неделю: у нас был pull request на версию с ошибкой, мы отревьювили 700 файлов 3 раза, но не смогли найти проблему.

Миграций базы уже не было, поэтому в качестве быстрого решения мы откатились на прошлую версию приложения. Это была ошибка. С откатом всё стало только хуже: Realm не мог прочитать свой файл из-за разницы версий самого Realm. Повезло, что мы обновили только 1% пользователей и вовремя остановили. Откат обошелся в 3000 крешей.

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

Стало ясно, что так выкатывать приложение нельзя, каждый раз что-то случается, в этом каждый раз задействован Realm. Конечно, на ошибках мы учились, но так подставлять нельзя ни пользователей, ни бизнес. Каждый новый релиз стал восприниматься как смертельное решение, страшно было катнуть даже маленький фикс с переводами. Есть ли креши? Никто не знает, он рандомный: UI-тесты иногда показывали, а иногда и по 5 раз проходили без проблем.

Команда устала, давление огромное, такое отношение к релизам терпеть нельзя. Стало понятно, что вылечить не получается, надо резать.

Можно ли было решить каждую из проблем? Да, конечно. Можно ли было угадать что-то наперед? Наверно да, вопрос миграций стоял давно. Надо ли это было делать срочно? Нууу, риски были… но 3 года же нормально работало, что случится?

Краткий итог критичных проблем:

проблемы с несколькими миграциями одного поля;

проблемы многопоточности в новой версии Realm.

Примечание. Забегая вперед скажу, что ошибку в Realm поправили в версии 5.3.5 20-го августа, а столкнулись мы 6-го. Фикс Realm вышел через две недели после наших проблем, но брейкинчедж появился 16 мая — проблему починили только спустя 3 месяца. Нам просто повезло, что мы не обновились раньше.

Как «продали» бизнесу удаление Realm

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

iOS команда не нашла аргументов за то, чтобы оставить Realm. При этом он мог нам заблокировать любой релиз новым неожиданным образом.

Увы, тут было не до продажи — просто поставили перед фактом.

Выпиливание Realm не та задача, которую можно сделать в фоне, да ещё и в конечный период. Пришлось ставить ультиматум, что релизить в таком состоянии мы не можем, надо остановить разработку на какое-то время и выпилить целый слой в приложении. За дело берутся все команды, на тот момент это было 4 iOS-разработчика.

Естественно, первый вопрос от бизнеса — на сколько времени останавливаемся. Ответ — примерно месяц. Офигели все.

План работ по сносу

Делать такую задачу без плана самоубийство. Надо составить план задач и отслеживать прогресс.

Ключевые строки. Мы выписали ключевые строки, по которым можно отслеживать как много Realm используется в проекте. Это могло бы быть мерилом качества инкапсуляции Realm. Нашли 3300 мест. Погнали выпиливать.

Но такая верхнеуровневая метрика не рассказывает о сложности работы, только её количество.

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

корзина и детали заказа;

очередь синхронизации продуктов в корзине.

По каждому домену оценили сколько упоминаний их объектов, а потом всё сложили. Получилось 1500 мест.

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

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

Ревизии. Каждый день делали ревизию по количеству упоминаний, строили график нашей скорости. Дольше всего выпиливали Realm из меню, в нём было 26 видов объектов с 852 упоминаниями. Над ним работало 2 человека и потратили 112 человеко-часов.

Многие домены «очистились» меньше чем за день. Это было шоком: мы пару лет бесились с того, как сложно работает корзина, а оказалось, что отказаться от кеширования в Realm можно за несколько часов.

Как удаляли

Простое удаление. Прозвучит странно, но где-то просто оказался лишний код. Например, в корзине у нас есть очередь из продуктов, которые ещё не были отправлены на сервер. Она нужна, чтобы не потерять продукт при сетевой ошибке. Мы сохраняли эту очередь в Realm, чтобы продукты не терялись даже между запусками. Хорошо, что это предусмотрено, но реальный шанс так потерять данные очень низкий. Для скорости выпиливания мы отказались от бэкапа корзины.

Замена объектов. Мы уже начали упрощать работу с Realm оборачивая его в абстракцию репозитория. План был такой: про способ хранения знает только репозиторий, а в приложение он отдает только доменные модели. Переписать успели процентов 30, это сильно помогло при удалении. При переписывании мы весь слой старых репозиториев с Realm заменяли на самописные репозитории, которые конвертировали структуры моделей через Codable и сохраняли в файл. Данных у нас не так много, способ подходит.

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

Обычно работы по замене выглядели так:

Убираем наследование от Object, убираем всё @objc dynamic декларации у property, меняем класс на структуру (если надо).

Меняем запросы к Realm на обращение в наш репозиторий.

Правим «мелочи»: тесты, доступ.

Чистим: меняем типы property с сырых на доменные. Больше никаких непонятных Int, только Enum.

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

Ещё проблемы, которые нашли

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

Адреса. Они состоят из 3-х слоев: объект адреса, набор полей, которые его описывают, у каждого поля есть его тип. Например: нужна улица, её значение Ленина и она часть адреса Ленина 25. Простая система, но из-за обратных ссылок в коде можно было ходить по вложенности в любом порядке: не только 1-2-3, но и 1-2-1-2-3-2. Это сильно усложняло код. Написали тесты, поменяли структуру моделей, отрефакторили, теперь можно двигаться только в одном направлении 1-2-3 — читать стало проще.

Города. В нашем домене встречаются две модели городов:

короткая — нужна только для списка городов на старте приложения;

полная, которая подгружается после того, как выбрали город и нужна для работы приложения.

Оказалось, что в Realm они были описаны одной полной моделью, а данные могли как оказаться в ней, так и нет. При этом приложение стартовало с простой модели, а потом докачивало данные. Естественно, могло и не докачать, и приложение бы работало как есть.

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

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

Пауза: фидбек и переоценка сроков

После недели интенсивного рефакторинга мы взяли паузу: стабилизировали релиз, начали брать бизнес-задачи. За неделю мы сделали очень много — по доменам упоминание снизилось на 60%. В итоге у нас осталось 3 несвязанных домена: города, оценка заказа и очередь продуктов в корзине.

Релизить всё ещё было страшно, но мы были вдохновлены результатами и даже выпустили релиз.

С новым XCode мы получили новые проблемы с Realm, но и новые пути решений у нас тоже были:

Как нам казалось, до конца проекта оставалось пару недель, поэтому 2 из 3-х команд начали брать бизнес-задачи, а одна продолжила рефакторить проект.

Не так страшны первые 90% рефакторинга, как вторые 90%

В последнюю очередь мы меняли логику для городов. В городах всё оказалось сильно сложнее:

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

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

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

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

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

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

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

Раньше в приложении много где дублировался код:

Взять текущий идентификатор города.

Получить запись из базы по идентификатору.

Взять первый объект в ответе, это считаем текущим городом.

Повторить в каждом месте.

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

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

Примечание. О модульности и тестах напишу как-нибудь потом, подписывайтесь на канал.

Миграция

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

В этом нам сильно повезло: когда мы отказались от миграций в Realm, мы перенесли все нужные для работы ID в UserDefaults. Мы знали ID корзины или выбранного города, поэтому на старте нужно было только получить новые данные от API.

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

Храните критичные ID вне базы — пригодятся.

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

Чистка после Realm

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

Мы замерили размер удаляемых файлов: в основном меньше 15 МБ, но было и несколько пользователей с размером в 150 и даже 300 МБ. И это не девайсы тестировщиков.

Новое хранилище

Какие-то данные всё равно хочется хранить. Мы уже избавились от Realm-объектов, перевели все на доменные. Хочется использовать их так, чтобы больше не надо было конвертировать из одного типа в другой только для хранения. Core Data таким образом тоже не подходит.

Мы собрали требования к хранению:

Хотим работать с доменным объектами.

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

Хранить можно в памяти или с кешем на диск. Приложение должно работать даже если на диске нет места. Кеш на диске опционален.

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

Объемы данных всегда небольшие (меньше мегабайта) и слабо связанные — реляционная БД не нужна.

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

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

SingleRepository хранит один объект.

Хранит только модель ProfileModel.

Хранит объект в памяти и кеширует на диск.

Ещё есть InMemoryStorage и FileStorage. Для хранения на диске модель должна реализовать протокол Codable, а для хранения в памяти это не нужно. Для доменной модели это вполне подходит и легко поддерживать. Теперь отдельную модель для записи в базе создавать не нужно.

Коллекция пиццерий хранится в CollectionRepository: синтаксис похож, только наследуемся от другого класса.

Примечание. Про устройство рассказывать долго: там и box typing, и работа с асинхронностью. Пишите в комментарии, если интересно узнать как работает внутри.

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

Мониторинг

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

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

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

Крешрейт к Новому годы мы довели до 99.95%. Можно улучшать ещё, ведь теперь креши не в рандомных местах Realm, а только в нашем продукте и понятно как их чинить.

Результаты

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

Домены. От изначальной проблемы связанных доменов почти ничего не осталось: всё работает независимо, мы активно разделяем приложение на фреймворки. Работать с такими модулями удобно: быстро компилируются, мало зависимостей и связей, понятная ответственность, легче тестировать. Можно даже из одного модуля создать отдельное приложение-витрину и написать для него UI-тесты.

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

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

Объём. Приложение уменьшилось на 8 МБ от Realm, запустили процесс по ревизии размера и уменьшили ещё на 10 МБ за счет бандла. Начали трекать размер приложения при каждом релизе.

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

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

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

Блокировки. Перестали блокироваться релизами Realm при обновлении Xcode, смогли обновить Cocoapods и поды на XCFramework.

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

Realm сложный инструмент и его надо уметь обслуживать. Простота интеграции бывает обманчива.

Больше новостей про разработку в Додо Пицце я пишу в канале Dodo Pizza Mobile. Также подписывайтесь на чат Dodo Engineering, если хотите обсудить эту и другие наши статьи и подходы.

Интересное по теме:

— Как заблокировать приложение с помощью runBlocking. Актуально для Андроид-разработки.
— О том, над чем в целом мы тут работаем: монолит, монолит, опять монолит.
— Кратко об истории Open Source — просто развлечься (да и статья хорошая).

А если хочешь присоединиться к нам в Dodo Engineering, то будем рады — сейчас у нас открыты вакансии для iOS-разработчиков (а ещё для Android, frontend, SRE и других). Присоединяйся, будем рады!

Источник

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

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

Рубрика: Администрирование / How To