Multitenancy
Дополнение позволяет создавать мультитенантные приложения Jmix, в которых данные нескольких тенантов хранятся в одной базе данных. Один экземпляр приложения может обслуживать несколько тенантов, определяемых как группы пользователей, изолированные друг от друга и имеющие доступ только к определенным (часто только для чтения) данным.
В мультитенантном приложении Jmix существует две основные категории данных:
- 
Общие данные: - 
Данные, которые используются совместно всеми тенантами в приложении. 
- 
Тенанты имеют доступ только для чтения к этим общим данным, которые являются общедоступными, но не могут изменяться отдельными тенантами. 
 
- 
- 
Данные конкретного тенанта: - 
Данные, специфичные для каждого тенанта, которые не видны и недоступны другим тенантам. 
- 
Тенанты имеют полный доступ к своим собственным данным, позволяя им взаимодействовать и изменять эти данные по мере необходимости, не затрагивая данные, принадлежащие другим тенантам. 
 
- 
Реализуя мультитенантность в приложениях Jmix, разработчики могут эффективно управлять сервисами и предоставлять их множеству клиентов или групп пользователей, обеспечивая изоляцию данных, безопасность и персонализированный контроль доступа в соответствии с конкретными потребностями каждого тенанта. Этот подход оптимизирует организацию данных и доступ к ним, сохраняя конфиденциальность и целостность данных в различных группах пользователей.
| Дополнение Generic REST не полностью поддерживает мультитенантность. Оно не разделяет экземпляры сущностей по тенантам. | 
Установка
Для автоматической установки через Jmix Marketplace следуйте инструкциям в разделе Дополнения.
Для ручной установки добавьте следующие зависимости в ваш build.gradle:
implementation 'io.jmix.multitenancy:jmix-multitenancy-starter'
implementation 'io.jmix.multitenancy:jmix-multitenancy-flowui-starter'Как это работает
В вашем проекте специфичные для тенанта сущности должны включать строковый атрибут, аннотированный @TenantId. Когда пользователь тенанта загружает эти сущности, фреймворк применяет условие WHERE на основе атрибута tenant-id к JPQL-запросу, чтобы выбрать только данные, относящиеся к тенанту пользователя. Кроме того, атрибут tenant-id автоматически присваивается тенанту текущего пользователя при сохранении новых сущностей.
| Автоматическая фильтрация для нативного SQL отсутствует, поэтому пользователи тенанта не должны иметь доступ к любым функциям, предоставляющим доступ к нативному SQL или коду Groovy (JMX Console, создание отчетов и т.д.). | 
В вашем проекте сущность User должна включать атрибут tenant-id. Этому атрибуту должно быть присвоено определенное значение для всех пользователей тенанта. Пользователи без значения в этом атрибуте (те, кто не связан с каким-либо конкретным тенантом) могут получать доступ к данным всех тенантов. Такая настройка подходит для глобальных администраторов, которые отвечают за настройку тенантов и наблюдение за всей системой.
Следующие сущности модулей Jmix имеют атрибут sysTenantId и поддерживают мультитенантность:
- 
EntityLogItem
- 
SendingMessage
- 
SendingAttachment
- 
Report
- 
ReportGroup
- 
ResourceRoleEntity
- 
RowLevelRoleEntity
- 
FilterConfiguration
Управление тенантами
Дополнение предоставляет экран Multitenancy → Tenants, который позволяет глобальным администраторам создавать и изменять тенанты.
Сущность регистрации тенанта имеет два атрибута:
- 
Tenant id - идентификатор, используемый в специфичных для тенанта сущностях. Не может быть изменен после создания. 
- 
Tenant name - описательное имя тенанта. 
 
Пользователи тенанта
В мультитенантных приложениях пользователи в разных тенантах могут иметь одинаковые логины. Чтобы гарантировать уникальность атрибута username во всем приложении, пользователям тенанта следует регистрироваться с логином, включающим префикс tenant-id. Например, если есть два разных пользователя с именем Alice в тенантах t1 и t2, их логины должны быть t1|alice и t2|alice соответственно.
Пользователи тенанта могут входить в приложение, указывая полный логин, включающий tenant-id, например, t1|alice.
| Вы можете реализовать собственную схему уникальных логинов вместо описанного выше метода. | 
Настройка пользователей
В этом разделе мы объясним процесс настройки управления пользователями и аутентификации в вашем проекте для поддержки мультитенантности.
- 
Добавьте строковый атрибут в вашу сущность Userи аннотируйте его аннотацией@TenantId:@TenantId @Column(name = "TENANT") private String tenant; public String getTenant() { return tenant; } public void setTenant(String tenant) { this.tenant = tenant; }
- 
Добавьте колонку tenantв таблицу данных вuser-list-view.xml:<column property="tenant"/>
- 
Добавьте поле для выбора тенанта в user-detail-view.xml:<comboBox id="tenantField" property="tenant" readOnly="true"/>
- 
Добавьте следующее в класс UserDetailView:@ViewComponent private JmixComboBox<String> tenantField; @Autowired private TenantProvider tenantProvider; @Autowired private MultitenancyUiSupport multitenancyUiSupport; @Subscribe public void onInit(final InitEvent event) { // ... tenantField.setItems(multitenancyUiSupport.getTenantOptions()); } @Subscribe public void onInitEntity(final InitEntityEvent<User> event) { // ... tenantField.setReadOnly(false); } @Subscribe public void onBeforeShow(final BeforeShowEvent event) { // ... String currentTenantId = tenantProvider.getCurrentUserTenantId(); if (!currentTenantId.equals(TenantProvider.NO_TENANT) && Strings.isNullOrEmpty(tenantField.getValue())) { tenantField.setReadOnly(true); tenantField.setValue(currentTenantId); } } @Subscribe("tenantField") public void onTenantFieldComponentValueChange(final AbstractField.ComponentValueChangeEvent<JmixComboBox<String>, String> event) { usernameField.setValue( multitenancyUiSupport.getUsernameByTenant(usernameField.getValue(), event.getValue()) ); }
- 
Чтобы разрешить использование идентичных логинов в различных тенантах, как было объяснено ранее, включите следующий фрагмент кода в ваш класс LoginView:@Autowired private MultitenancyUiSupport multitenancyUiSupport; private Location currentLocation; (1) @Override public void beforeEnter(BeforeEnterEvent event) { currentLocation = event.getLocation(); super.beforeEnter(event); } @Subscribe("login") public void onLogin(final LoginEvent event) { String username = multitenancyUiSupport.getUsernameByLocation(event.getUsername(), currentLocation); try { loginViewSupport.authenticate( AuthDetails.of(username, event.getPassword()) .withLocale(login.getSelectedLocale()) .withRememberMe(login.isRememberMe()) ); } catch (BadCredentialsException | DisabledException | LockedException | AccessDeniedException e) { log.info("Login failed", e); event.getSource().setError(true); } }1 Используйте объект com.vaadin.flow.router.Locationдля работы с текущим URL.
Настройка безопасности
При настройке ролей для пользователей тенанта не включайте атрибуты tenant-id в политики атрибутов сущностей, чтобы скрыть их от пользователей. Например, если сущность Customer специфична для определенного тенанта и включает атрибут tenant, аннотированный @TenantId, роль, предоставляющая доступ к этой сущности, должна явно указывать атрибуты и исключать атрибут tenant:
@ResourceRole(name = "UsersRole", code = UsersRole.CODE, scope = "UI")
public interface UsersRole {
    String CODE = "users-role";
    @EntityAttributePolicy(entityClass = Customer.class,
            attributes = {"id", "name", "region", "version"},
            action = EntityAttributePolicyAction.MODIFY)
    @EntityPolicy(entityClass = Customer.class, actions = EntityPolicyAction.ALL)
    void customer();
}