Embedded element Vs. Reference Vs. Association
Abstract Entity and Concrete implementations
Consider a case where you want to model an entity (Supplier) which has a list of addresses. There are four different ways you can design your domain model. Each one has advantages and drawbacks that you need to be aware of. Choosing the right approach is depends on the specific use case.
1 2 3 4 5 6 7 8 9 10 11 | @Accessible public class Supplier extends JelloEntity { @Expose @KeyElement String name; @Expose List<Ref<Address>> addresses; } @Accessible class Address extends JelloEntity{ @Expose @KeyElement String street; @Expose @KeyElement String city; @Expose @KeyElement String zipcode; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Accessible public class Supplier extends JelloEntity { @Expose @KeyElement String name; @Association(mappedBy="supplier") @Expose List<Address> addresses; } @Accessible class Address extends JelloEntity{ @Expose @KeyElement String street; @Expose @KeyElement String city; @Expose @KeyElement String zipcode; @Expose Ref<Supplier> supplier; } |
@GroupElement
field1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Accessible public class Supplier extends JelloEntity { @Expose @KeyElement String name; @Association @Expose List<Address> addresses; } @Accessible class Address extends JelloEntity{ @Expose @KeyElement String street; @Expose @KeyElement String city; @Expose @KeyElement String zipcode; @Expose @GroupElement Ref<Supplier> supplier; } |
From REST API perspective, the data will look the same as association by backreference.
See Strong Association for more details.
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Accessible public class Supplier extends JelloEntity { @Expose @KeyElement String name; @Expose List<Address> addresses; } @Accessible(Role.AS_EMBEDDED_ONLY) class Address extends JelloEntity{ @Expose @KeyElement String street; @Expose @KeyElement String city; @Expose @KeyElement String zipcode; } |
Jello support of polymorphism helps to keep the domain model structure cleen and aligned with the problem you are trying to solve. It is very common to define an abstract entity with some concrete derived entities.
Consider the following example:
1 2 3 4 5 6 7 8 9 10 11 | @Accessible abstract class Person extends JelloEntity { @Expose @KeyElement String name; } @Accessible class Stuff extends Person { @Expose Integer salary; } @Accessible class Student extends Person { @Expose Double grade; } |
The Person abstract entity meta data will listed the accessible concrete entities and when clicking the New button from the abstract entity view, you will have to select a concrete entity to create.
Jello store your data in the Google Cloud datastore which is a highly-scalable NoSQL database. It is important to understand the characteristics of such database and how to providing a consistent user experience while leveraging the eventual consistency nature of the database.
For a discussion about Eventual Consistency, please read the article Providing a Consistent User Experience and Leveraging the Eventual Consistency Model to Scale to Large Datasets.
To support transactions with Jello, you need to annotate one (and one only) of the field in the entity with @GroupEntity
.
During bootstrap, Jello will inject the objectify @Parent
annotation which in turn will add an ancestor to the entity key.
Lets look again on the Supplier-Addresses strong association example:
We starts by defining a @GroupElement
supplier field in the Addresses Entity.
This will establish a strong association between a Supplier and its Addresses and will make sure both entities are saved under the same entities group and hence, will allow transactions.
1 2 3 4 5 6 7 8 9 10 11 | @Accessible public class Supplier extends JelloEntity { @Expose @KeyElement String name; @Association @Expose List<Address> addresses; } @Accessible class Address extends JelloEntity{ ... @Expose @GroupElement Ref<Supplier> supplier; } |
Upon deletion of a Supplier, Jello will attempt to delete all its associated Addresses as well. To avid a situation where, as a result of a DB or application level error, not all of the associated Addresses will actually deleted, you might consider to execute the deletion inside a transaction.
DELETE: ../jello/datat/app/Supplier(1234)?$asTransaction
The best way to validate entities data is by overriding the default JelloEntity empty BeforeSave
method.
Any IllegalRequestResource
exception thrown with field's name argument will be serialized as follow:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | @Accessible public class ServerSideValidation extends JelloEntity { @Expose Integer num; @Expose String text; @Override protected JelloEntity beforeSave() throws IllegalRequestResource { IllegalRequestResource validationError = new IllegalRequestResource("Failed to save entity (" + getID() + ")" ); Boolean thereIsError = false; if(num != null && num > 3) { thereIsError = true; validationError.forField("num", "num is too big"); } if(text != null && text.contains("fishy")) { thereIsError = true; validationError.forField("text", "Something is fishy with the text"); } if(thereIsError) { throw validationError; } return this; } } |
1 2 3 4 5 6 7 8 9 | { "error" : { "message":"Failed to save entity", "validations":{ "num":"num is too big", "text": "Something is fishy with the text" } } } |
Was this page helpful? Let us know how we did:
Last updated March 20, 2019.