GraphQL complexity to support update/create worth it? - java

I´m having a relative complex, hierarchical data model in my Spring application and I need to query it as well as update all entities by letting the client pass the changed entity to the server.
Using GraphQL for querying solves exactly my needs, but to enable update and create capabilities however, I´d need to duplicate all my entities as *.graphqls files won´t support using "type" elements as "input" elements.
For the newly created Input element, I´d need to define a Java Object matching it too.
For queries I´d end up with
Portfolio.java
#Entity
public class Portfolio {
#Id
private int id;
private String name;
....
}
and portfolio.graphqls
type Portfolio {
id: ID!
name: String
....
}
But for updates I´d end up with
Portfolio.java
#Entity
public class Portfolio {
#Id
private int id;
private String name;
....
}
PortfolioInput.java
#Entity
public class PortfolioInput {
#Id
private int id;
private String name;
....
}
portfolio.graphqls
type Portfolio {
id: ID!
name: String
....
}
input PortfolioInput {
id: ID!
name: String
....
}
type PortfolioMutation{
updatePortfolio(input: PortfolioInput):Portfolio
}
My problem with that is, I need to keep 4 entities in sync with their fields now, just because GraphQL does not allow using the type as input and I´d need to do that for many other entities as well.
Is there any other solution, or do people go the extra mile and accept the additional complexity, or do you simply switch to REST for POST/PUT operations and only use GraphQL for GET?

Related

Spring Data Neo4J reference related nodes without overwriting them on save

I'm struggling to write this, so I may have to give an example to help explain the problem I'm experiencing.
Say we have nodes of three types (these nodes may have more relationships of their own, e.g. Product Family, has product manager):
Product
Product Family
Battery
With these relationships
A product can be be in 0 or more families
A product can have 0 or more batteries.
When using spring-data-neo4j and saving a new Product, I wish to include these relatiopnships, such as the batteries they require and the product family they belong to. However if I only supply say an ID rather then a fully populated object, it overwrites this object along with properties and relations accordingly.
This isn't great as it means that I have to end up sending a fully populated object, with all it's relations everytime I wish to save something, and some of these relations may go quite deep.
My domain is as follows:
#Node
public class Product {
#Id
#GeneratedValue(generatorClass = SnowflakeGenerator.class)
private Long productId;
private String name;
#Relationship(type = "REQUIRES_BATTERY", direction = OUTGOING)
private List<Battery> batteryList;
#Relationship(type = "IN_FAMILY", direction = OUTGOING)
private List<ProductFamily> productFamilyList;
}
#Node
public class Battery {
#Id
#GeneratedValue(generatorClass = SnowflakeGenerator.class)
private Long batteryId;
private String name;
}
#Node
public class ProductFamily {
#Id
#GeneratedValue(generatorClass = SnowflakeGenerator.class)
private Long familyId;
private String name;
}
This could very well by from coming from a Relational Database mindset and is a 'limitation' of using Neo4J.
TLDR When persisting somethign in Neo4J using spring-data how can I save just a relationship, rather than a whole related Node.
You can make use of projections in Spring Data Neo4j. (https://docs.spring.io/spring-data/neo4j/docs/current/reference/html/#projections)
This gives you the option to put a "mask" on the object tree, you want to persist (and what should stay untouched).
For example in your case:
interface ProductProjection {
// without defining e.g. String getName() here, SDN would not ever touch this property.
List<BatteryProjection> getBatteryList();
List<ProductFamilyProjection> getProductFamilyList();
}
interface BatteryProjection {
String getName();
}
interface ProductFamilyProjection {
String getName();
}

Can we use Composite Primary Key Mapping in spring data elastic search

I have an entity 'Product' and I want the primary key in ES to be used as a combination of 'id' and 'name' attributes. How can we do that using spring data elastic search.
public class Product {
#Id
private String id;
#Id
private String name;
#Field(type = FieldType.Keyword)
private Category category;
#Field(type = FieldType.Long)
private double price;
#Field(type = FieldType.Object)
private List<ValidAge> age;
public enum Category {
CLOTHES,
ELECTRONICS,
GAMES;
}
}
One way to achieve this would be the following:
first rename your id property, I changed it to documentId here. This is necessary, because in Spring Data
Elasticsearch an id-property can be either annotated with #Id or it can be namend id. As there can only be one
id-property we need to get this out of the way. It can have the name id in Elasticsearch, set by the #Field
annotation, but the Java property must be changed.
second, add a method annotated with #Id and #AccessType(AccessType.Type.PROPERTY) which returns the value you
want to use in Elasticsearch.
third, you need to provide noop-setter for this property. This is necessary because Spring Data Elasticsearchsoe
not check the id property to be read only when populating an entity after save or when reading from the index.
This is a bug in Spring Data Elasticsearch, I'll create an issue for that
So that comes up with an entity like this:
#Document(indexName = "composite-entity")
public class CompositeEntity {
#Field(name="id", type = FieldType.Keyword)
private String documentId;
#Field(type = FieldType.Keyword)
private String name;
#Field(type = FieldType.Text)
private String text;
#Id
#AccessType(AccessType.Type.PROPERTY)
public String getElasticsearchId() {
return documentId + '-' + name;
}
public void setElasticsearchId(String ignored) {
}
// other getter and setter
}
The repository definition would be straight forward:
public interface CompositeRepository extends ElasticsearchRepository<CompositeEntity,
String> {
}
Remember that for every method that needs an Elasticsearch Id, you'll need to create like it's done in the entity
class.
I am not sure about spring data elasticsearch but spring jpa provides the facility of defining composite primary key by using #IdClass where we can define a separate class(let us say class A) in which we can define all the fields which we want to be a part of composite key Then we can use #IdClass(A.class) in entity class and use #Id annotation on all the fields which should be the part of the composite key
you can refer to this article, although I am not sure whether the same concept will be applicable for spring data es - https://www.baeldung.com/jpa-composite-primary-keys

Creating new entity related with extisting one

I am trying to figure it out how to do this proper way. Let's say I have entity Employee like that:
#Entity
public class EmployeeEntity{
#Id
private Long id;
private String username;
private String password;
private List<AddressEntity> addresses;
private DepartmentEntity department;
}
Now let's say some AddressEntity and DepartmentEntity are already created so I just want to point it. Controller would look like this:
#RestController
public class EmployeeController{
#Autowired
private EmployeeService;
#PostMapping
public EmployeeDto createEmployee(#RequestBody EmployeeDto employee){
return employeeService.createEmployee(employee);
}
}
And DTO:
public class EmployeeDto{
private Long id;
private String username;
private String password;
private List<AddressDto> addresses;
// private List<Long> addressesIds;
private DepartmentDto department;
// private Long departmentId;
}
So what bothers me is how properly transfer data from request to service layer and to response.
should DTO be object 1:1 same like entity?
or with additional values, like ids of others related objects?
or DTO is just concept and as well I can use custom request/response for every occasion? This would be handy but is it the way it should be done? There would be plenty of one-case-use classes.
Crating new entity is first problem but how about updating? If I would like to update just Employee username, I shouldn`t pass all rest of the objects so ids maybe? And it should be custom UpdateEmployeeRequest with only updatable fields or DTO with all data like password?
Sorry if I messed up a little. Too much new knowledge and I feel like I go round and round like a child in the fog...
should DTO be object 1:1 same like entity? or with additional values, like ids of others related objects?
Not necessary. DTOs are mostly to pass data to view layer. You can wrap data from multiple entities and send in one DTO to view.
or DTO is just concept and as well I can use custom request/response for every occasion? This would be handy but is it the way it should be done? There would be plenty of one-case-use classes.
Yes. It is like custom request/response for every occasion (data transfer to view and from view).
Crating new entity is first problem but how about updating? If I would like to update just Employee username, I shouldn`t pass all rest of the objects so ids maybe? And it should be custom UpdateEmployeeRequest with only updatable fields or DTO with all data like password?
Pass the required minimum fields and use same DTO on Create/Update (Id with field to update on update and other fields on Create).
Example dto for create:
username : "some user",
password : "some password",
... other fields
Example JSON for update username:
id: 1,
username : "some user",

Elasticsearch: search for two different document in a single query

I have two entities Merchant and Customer:
public class Merchant{
private UUID id;
private String name;
//... other fields and getters/setters
}
public class Customer{
private UUID id;
private String name;
//... other fields and getters/setters
}
These two entities are sightly different from each-other.
What I'am trying to to do is when I search with the term "John" I want to get both a merchant named "John Market" and a customer called "John Smith".
To achieve this I indexed these entities to a single index.
#Document(indexName = "merchant_customer_index", type = "merchantorcustomer")
public class MerchantOrCustomer {
#Id
private UUID id;
private String name;
private int type;
//...
My query can return both Merchant and Customer:
List<MerchantOrCustomer> result = elasticsearchTemplate.queryForList(nativeSearchQuery, MerchantOrCustomer.class);
I distinguish them programmatic(if(result.get(i).getType() == 0 we received Merchant else Customer)
Then use their id to extract actual object from relational db.
I searched a lot, but couldn't find anything that can help to estimate if it is a good practice. Is it a good practice?
Please, give me a hint if there is a better way.
There doesn't seem to be anything wrong with what you did unless there is some collusion as mentioned by #Ivan in comments.
Here is another possible way to do if you were using elasticTemplate- Spring Data Elasticsearch: Multiple Index with same Document or if you are using queryBuilder - https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/java-search.html

Crnk JsonApiRelation, OneToMany and filtering implementation

I use crnk (JSON-API) in java project and I have 3 questions regarding its usage with spring boot and jpa - haven't found exact implementation details in documentation.
For example, I have 2 entities and respective tables:
#Entity
#JsonApiResource(type = "employee")
public class Employee {
#Id
#JsonApiId
private int id;
private String name;
#ManyToOne
#JoinColumn(name = "typeId")
private EmployeeType employeeType; //stored in table as typeId
}
#Entity
#JsonApiResource(type = "type")
public class EmployeeType {
#Id
#JsonApiId
private int id;
private String typeName;
private int salary;
}
How should JsonApiRelation be introduced in order to be able to call "/employee/1" and "/employee/1/type" urls?
For example there is one more entity.
#Entity
#JsonApiResource(type = "project")
public class Project {
#Id
#JsonApiId
private int id;
private String supervisorName;
private String projectName;
}
First, I'd like to have List of Projects for each Employee, where he is a supervisor, joint by name and have it listed as attribute in Json.
Tried implementing it with #OneToMany and #JoinColumn annotations but got StackOverflowException. How could this be implemented. And second, how could this be implemented with Relation? Like "/employee/1/projects" url.
How should I implement custom filtering of results for findAll method? For example, I have a List of all Employees, but I'd like to exclude some of them from the response. Which class/method should be introduced for this behaviour?
#JsonApiRelation annotation should not be necessary. Crnk will detect the #ManyToOne annotation and map it accordingly.
in case of crnk-jpa it is sufficient to specify all relationships in JPA. Matching JSON API relationships. So your approach seems good. What was the StackoverflowException stacktrace? (next to the examples, there are also many example entities in crnk-jpa)
I would make use of a decorator. See http://www.crnk.io/documentation/#_request_filtering. RepositoryDecoratorFactory allows to place a custom repository between the caller and crnk-jpa (or any other kind of repository). There you can do any kind of modification perform (maybe) calling the "real" repository. => Will add an example for this
feel free also make open up tickets in crnk for any documentation/example clarifications.

Categories