Переопределение сущности на бэкенде ломает клиентское приложение REST DataStore

jmix 2.8.2

В нашем проекте есть многомодульное fullstack приложение (типа такого mariodavid/jmix-insurance: Jmix Sample Multi Module Example of the Insurance Domain) и дополнительно клиентское приложение на REST DataStore. Модули основного приложения опциональны, то есть при развертывании мы можем не включать какие-то модули и их не будет в приложении.

Один из модулей переопределяет сущности других модулей через @ReplaceEntity.
Например была сущность:

@JmixEntity
@Entity(name = "OriginalEntity")
public class OriginalEntity {
    ...
}

А стала:

@JmixEntity
@Entity(name = "ReplacedEntity")
@ReplaceEntity(OriginalEntity.class)
public class ReplacedEntity extends OriginalEntity {
    ...
}

В клиентском приложении REST DataStore создано dto для OriginalEntity :

@JmixEntity(name = "OriginalEntity")
@Store(name = "...")
public class OriginalEntity {
...
}

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

При такой конфигурации возникает ошибка на клиенте, при запросе сущности OriginalEntity:

java.lang.IllegalArgumentException: MetaClass not found for ReplacedEntity

Насколько я понимаю, бэкенд на запрос OriginalEntity возвращает ReplacedEntity, а клиент не может у себя найти такой мета класс.
Также пробовал

@JmixEntity
@RestDataStoreEntity(remoteName= "OriginalEntity")
@Store(name = "...")
public class OriginalEntity {
...
}

И

@JmixEntity(name = "OriginalEntity")
@RestDataStoreEntity(remoteName= "OriginalEntity")
@Store(name = "...")
public class OriginalEntity {
...
}

Не работает.

Работает только вариант если на клиенте указать название переопределенной сущности:

@JmixEntity(name = "ReplacedEntity")
@Store(name = "...")
public class OriginalEntity {
...
}

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

Также это просто не удобно и не надежно при каждом переопределении на бэке проверять/исправлять сущности на клиенте.

Пока что пришел к такому воркэраунду:

  1. На бэке сделал RestMethod, который принимает список мета классов (имен) и возвращает названия соответствующих переопределенных сущностей.
@RestService("ReplacedEntitiesService")
public class ReplacedEntitiesService {
	private final Metadata metadata;

	public ReplacedEntitiesService(Metadata metadata) {
		this.metadata = metadata;
	}

	@RestMethod
	public Map<String, String> getReplacedEntityNames(Collection<String> entityNames) {
		Map<String, String> replacedEntityNames = new HashMap<>();
		for (var name : entityNames) {
			String metaClassName = metadata.getClass(name).getName();
			if (Objects.equals(metaClassName, name)) continue;
			replacedEntityNames.put(name, metaClassName);
		}
		return replacedEntityNames;
	}
}
  1. Клиент вызывает этот метод и сохраняет к себе в Session актуальные названия сущностей, полученные с бэка
Session session = metadata.getSession();
if (session instanceof SessionImplementation sessionImplementation) {
	List<String> localEntityNames = metadata.getClasses().stream().map(MetadataObject::getName).toList();
	Map<String, String> replacedEntityNames = replacedEntitiesService.getReplacedEntityNames(localEntityNames);
	for (var localName : replacedEntityNames.keySet()) {
		String replacedName = replacedEntityNames.get(localName);
		MetaClass localMetaClass = metadata.getClass(localName);
		sessionImplementation.registerClass(replacedName, localMetaClass.getJavaClass(), localMetaClass);
	}
} else {
	LOG.warn("Session expected to be an instance of '{}', but is '{}'",
			SessionImplementation.class, session.getClass());
}

Добрый день, Дмитрий!

Спасибо за подробное описание - воспроизвёл проблему на чистом стенде (официальный пример jmix-integrated-apps-sample, ветка release_2_8, обновил до 2.8.2) , предварительно причины проблемы следующие:

Обычная загрузка через REST DataStore (CRUD) к @ReplaceEntity устойчива: клиент десериализует ответ с опцией IGNORE_ENTITY_NAME и подставляет локальный DTO, поэтому _entityName из ответа игнорируется и списки/детали грузятся нормально.

Ошибка MetaClass not found for ReplacedEntity возникает только при вызове метода @RemoteService, который возвращает переопределённую сущность (или коллекцию). Там десериализация идёт иначе:

// io.jmix.restds.impl.service.RemoteServiceInvoker#getResultObject
result = entitySerialization.entityFromJson(resultJson, null);            
// metaClass = null, без IGNORE_ENTITY_NAME
// или
result = entitySerialization.entitiesCollectionFromJson(resultJson, null);

Целевой метакласс не передаётся и опции нет, поэтому EntitySerializationImpl.readEntity(...) читает _entityName = "ReplacedEntity" и вызывает metadata.getClass("ReplacedEntity"), которого в метамодели клиента нет → IllegalArgumentException. То есть десериализация результатов удалённых сервисов не повторяет толерантное поведение, уже реализованное для CRUD.

Завёл баг: https://github.com/jmix-framework/jmix/issues/5568 , Ваш обходной путь с регистрацией соответствия имён метаклассов указал как временное решение до фикса.

Спасибо!

С уважением,

Игорь

1 лайк