Open kubaseai opened 4 years ago
There are different angles to this ticket:
JsonAnyGetter/Setter
to store arbitrary (untyped/unknown data). It comes close to what you mention.include
section, For the simplified protocol flavor this comes quite close to how things work in MongoDB.which case are you looking more into?
I'm going to have main uber entity with 10-20 member fields which are lists of related entities. I tried HTTP post with include section, but didn't see related entities in repository create method. JsonAnySetter doesn't work for me because I want to use well defined Java objects/classes. Creation is very important for me. I need one HTTP call to save main entity and related ones. Doing 21 HTTP calls is not an option. I would like to have URL mainEntity/{id}/relatedEntity to fetch only some field. So I need standard relations plus bonus feature to create all entities in one call and store them in one document.
the truely resource-oriented approach might be to create a second "ueber" resource for the update case. but some short cut would still maybe be desirable to also update relationships. do you make use of the pure JSON:API (default) or the simplified crnk-plain-json-format?
I use default JSON:API.
ok, because for the other one, relationships are already inlined, so it comes quite close to mongo.
I think I would consider that additonal ueber resource. Seems simplest & most resource-oriented approach.
I would like to resolve it this way:
/**
* This class allows setting relational fields as plain JSON:API attributes
* during POST request. This is very useful with MongoDB if you want to store embedded
* entities in one document, but still be able to read and write them in JSON:API way.
*/
public class UberEntitySupportModule implements Module {
private static final String UBER_ENTITY_PROCESSOR_ATTR = "UBER_ENTITY_PROCESSOR";
private UberEntityDocFilter filter;
public UberEntitySupportModule(CrnkBoot crnkBoot) {
this.filter = new UberEntityDocFilter(crnkBoot);
}
@Override
public String getModuleName() {
return UberEntitySupportModule.class.getName();
}
@Override
public void setupModule(ModuleContext ctx) {
ctx.addFilter(filter);
}
public static final void install(CrnkBoot crnkBoot) {
crnkBoot.addModule(new UberEntitySupportModule(crnkBoot));
}
public static final void processResource(Object resource) {
RequestAttributes reqAttrs = RequestContextHolder.getRequestAttributes();
@SuppressWarnings("unchecked")
Consumer<Object> uberEntityProcessor = (Consumer<Object>) reqAttrs
.getAttribute(UBER_ENTITY_PROCESSOR_ATTR, RequestAttributes.SCOPE_REQUEST);
if (uberEntityProcessor!=null) {
uberEntityProcessor.accept(resource);
}
}
private static final class UberEntityDocFilter implements DocumentFilter {
private CrnkBoot crnkBoot;
public UberEntityDocFilter(CrnkBoot crnkBoot) {
this.crnkBoot = crnkBoot;
}
@Override
public Response filter(DocumentFilterContext ctx, DocumentFilterChain chain) {
RequestAttributes reqAttrs = RequestContextHolder.getRequestAttributes();
reqAttrs.setAttribute(UBER_ENTITY_PROCESSOR_ATTR,
new UberEntityResourcePostController(ctx, crnkBoot), RequestAttributes.SCOPE_REQUEST);
return chain.doFilter(ctx);
}
}
private static final class UberEntityResourcePostController extends ResourcePostController implements Consumer<Object> {
private DocumentFilterContext ctx;
public UberEntityResourcePostController(final DocumentFilterContext ctx, final CrnkBoot crnkBoot) {
this.ctx = ctx;
this.context = new ControllerContext(crnkBoot.getModuleRegistry(), new Supplier<DocumentMapper>() {
@Override
public DocumentMapper get() {
return crnkBoot.getDocumentMapper();
}
});
}
@Override
public HttpMethod getHttpMethod() {
return HttpMethod.valueOf(ctx.getMethod());
}
@Override
public void accept(Object instance) {
if (ctx.getRequestBody().getData().isPresent()) {
Resource res = (Resource) ctx.getRequestBody().getData().get();
ResourceInformation ri = new UberEntityResourceInformation(
ctx.getQueryAdapter().getResourceInformation());
setAttributes(res, instance, ri, ctx.getQueryAdapter().getQueryContext());
}
}
}
private static final class UberEntityResourceInformation extends ResourceInformation {
public UberEntityResourceInformation(ResourceInformation ri) {
super(new TypeParser(), ri.getImplementationType(), ri.getResourceType(), ri.getSuperResourceType(),
ri.getFields(), ri.getPagingSpecType());
}
@Override
public ResourceField findAttributeFieldByName(String name) {
ResourceField field = super.findAttributeFieldByName(name);
if (field==null) {
ResourceField anyField = super.findFieldByName(name);
if (anyField!=null && anyField.getResourceFieldType() == ResourceFieldType.RELATIONSHIP) {
field = anyField;
}
}
return field;
}
}
}
Is there a high probability that used API is stable?
I'm using Crnk with Spring Boot and MongoDB. I've got:
Inside MainEntity I've got JsonApiRelation for children, however I want to be compliant with MongoDB recommendation for not dividing one logical document into smaller pieces, because MongoDB is not relational database. I created 'virtual' Repository for children where access is delegated to repository handling MainEntity:
I would like to request a feature to store related entity together with main entity as one MongoDB document. In crnk-core-3.1.20191020144522 this can be accomplished by modification of io.crnk.core.engine.internal.dispatcher.controller.ResourceUpsert.setAttribute():
Above it's a naive implementation for MongoDB. To be usable for everyone there should be a switch in configuration. Can you accept this feature request? I checked master and stable branches and code is different that currently used in my project. I tried using ResourceUpsert from stable, and it looks like setAttribute receives filtered attribute, so ResourceField is always attribute and my code doesn't work. Can you help with understanding where should I change code now, so I can prepare pull request? For proper pull request I would also need to know how to create and use configuration switch.