One quick question. What if the type of a field in a class could be accessed like a static field? Of course, it would mean that we would have to give up the current syntax reserved for static access etc., but we could see some interesting opportunities. For example, the type of a field “private String name;” of a “Person” class could be accessed with “Person.name.type”? What if the field “Person.name” actually stored all relevant metadata for that object? Crazy? Interested? Please keep reading…
We can not really utilize metadata of objects at runtime. By metadata I mean information that is, or at least should be, bound to objects of any class. For example, have you ever tried to infer a type of generic class at runtime? Quickly you will learn something called “type erasure”. A typical solution to this problem is to use explicit class type. Java programming language is pretty old (over 20 years old now) and because of the promise of the backwards compatibility some things are not likely to change.
But there is always hope, and let me talk about my hopes now. What is the metadata model then? Let’s take an example. Beans (POJOs) use getters and setters for attaching arbitrary properties to them. Some time ago I wrote about how to improve POJOs into ROJOs. There are some additional improvements we can make to make code more readable and easier to maintain: welcome metadata model.
public class AnonymousPerson {
String alias = String.EMPTY(); // Note! EMPTY is a constant but needs parentheses now...
Integer age = Integer.Zero();
TruePerson hidden = TruePerson.getDefault();
// note also the naming of the parameters!
// get value objects: alias() and age()
// set value objects: alias(String newAlias) and age(Integer newAge)
// get reference objects: getTruePerson()
// set reference objects: setTruePerson(TruePerson newTruePerson)
}
So you notice a lot of oddities. First, every object is accessed with the help of parentheses. Second, value objects have no “get” or “set” prefix: only reference objects have them. The reason is that whatever data is exposed to user it is (and must be!) in String format. Another reason is that there should be no (or minimal) reason to expose any object references (or database foreign key references either) to the user. Sadly, this metadata model can not be directly implemented since because it does not compile. Third, an invisible thing that I will elaborate next: the metadata container.
In the previous example AnonymousPerson.alias.name would return the name of the field i.e. “alias” as a string: note no parentheses now! Current syntax of AnonymousPerson.getClass() would translate to “AnonymousPerson.alias.type” and it would return type of the class (no runtime erasure!). “AnonymousPerson.alias” itself (parent) acts as a metadata container for the class. It could also contain:
- “AnonymousPerson.alias.range” for definition or valid range for values. Since representation is String-based, the range should be defined as regex pattern!
- “AnonymousPerson.alias.default” for definition or default value. Since representation is String-based, the range should be defined as regex pattern!
- “AnonymousPerson.alias.unit” for definition of unit for the value. Again, a string. Unit has also prefix and post fix, for example a metric ton (tonne) is equivalent to one megagrams or Mg where M is ‘prefix’ and gram is ‘unit’
- “AnonymousPerson.alias.regexpString” pattern for converting a valid value into a string
- “AnonymousPerson.alias.stringRegexp” pattern for converting a string into a valid value (or error if conversion is not possible)
In general, data has some sort of a unit: either measure which refers to quantitative data, or dimension which refers to categorical data (e.g. product name, gender, time unit, etc.). Imaginary examples.
// Core Java (EMPTY String is same as "", and the default value for Strings!)
private String alias = String.EMPTY();
// JPA
@Column(AnonymousPerson.alias.name)
private String alias;
// JSON
JsonObject value = Json.createObjectBuilder()
.add(AnonymousPerson.alias.name, "John Doe")
.build()
// toString for a 'age' field prints out a string of 'age 35 year'
public String toString() {
return (AnonymousPerson.age.name + " " + this.age().toString() + " " +
AnonymousPerson.age.unit.prefix +
AnonymousPerson.age.unit.base +
AnonymousPerson.age.unit.postfix);
}
Speaking more about beans. Listing more improvements I have in my mind: what should they really consist of (please see also the ROJO link)?
getters and setters for value objects (however no get/set prefix i.e. DDD style!), x3 for different purposes
getters and setters for reference objects (not to be exposed directly to user!), x3 for different purposes
a builder for creating new objects ('new' keyword is allowed only to class itself!)
a renovator for "loading" old objects (with certain id, each bean is required to have a DDD style 'id'!)
extra methods like parser, validator, converter, etc. used especially with String operations
basic methods like equals, hashCode, toString, etc.
Few words about parser, validator, and converter methods. Loosely speaking, they convert a value object between actual value and string based value representation. For example, “3” can be parsed to “Integer(3)”, and “Integer(34)” can be (un)parsed into “34”. Any web application data that comes from the browser is text (in String format) so it is first parsed into a certain type: IMO there should be always a fully tested regexp parser for each class. Then the outcome of the regexp parsing is validated against defined value range and unit. If outcome is valid we have a successfully converted value!
The reverse direction is similar: a class has a type. We already know that the value we are trying to convert is valid (by requirement and definition). We can use toString-style methods, but we still want to use regexp too: the reason is that here we can define custom textual representation for the user before the data leaves the server. Converter methods are meant for converting between different types of beans or classes in general, but please note that you should stop using type casting because it is not either safe or well-defined operation whereas your custom implementation of the converter should be – after testing at least.
You must be logged in to post a comment.