Friday, February 1, 2013

Of Domain Modeling, Separation of Concerns, and How the JPA Annotations Fail at Both

        Not all of the JPA annotations are actually about mapping the entities to the database, or even about persistence at all. Some of them are intended to instrument the Java language for better modeling of the domain. Here's a simple example of two domain classes that are somehow associated with each other (accessor methods are omitted for clarity):
public class User {

  private String name;

  private Set<Role> roles;

}

public class Role {

  private String name;

}
        Observing those classes, can we unambiguously determine the relationship between them? A User has a set of Roles, that we may say for sure. But for all we know, this association may be either one-to-many or many-to-many, from the domain view point. There's no reverse association from the Role to the User, so we don't actually know if the same Role can be assigned to several different Users or not. Well, let's suppose we wanted to model a many-to-many relationship. Let's add the reverse link to the Role class and see if it helps, though we may have no use for the reverse connection in the context of our model at all.
public class User {

  private String name;

  private Set<Role> roles;

}

public class Role {

  private String name;

  private Set<User> users;

}
        Does it feel better now? Oh, surely we do have a many-to-many association between those two classes now... or not? Actually, we made quite a heck of an assumption that the User.roles set and the Role.users set point at each other, i.e., they model the same association, but that may certainly not be the case. For example, User.roles set may be a set of Roles that a User has as a user of a system, but the Role.users set may be a completely unrelated set in the context of the domain model.

        For example, a Role may have a set of Users that have an authority to grant that role. Surely in this case we could make a better job of naming those two fields somehow differently, so that noone would confuse them as representing the same association. But now we have two different associations, and we still have no clue as of what their actual relationship is. We're still missing the point of modeling the domain with the Java programming language which is considered to be an Object-Oriented language — seemingly a right choise for the job!

        That's where the association annotations come in. Here's the annotated version of the first case — unidirectional association:
public class User {

  private String name;

  @ManyToMany
  private Set<Role> roles;

}

public class Role {

  private String name;

}
        Now we see clearly that the association between those entities is many-to-many, and there's no ambiguity in it. As for the second case:
public class User {

  private String name;

  @ManyToMany
  private Set<Role> roles;

}

public class Role {

  private String name;

  @ManyToMany(mappedBy = "roles")
  private Set<User> users;

}
        The mappedBy attribute for the @ManyToMany annotation in the Role class is what makes those two sets "click" together. The "roles" string is the name of the User class field (not a database field!). OMG, is that a String pointer to a Java class field? Yeah, yeah, we should probably have a more obvious and compile-checked pointer to the field from the other side, but, alas, the Java programming language is so poor that it does not leave us any options. Though some IDEs may help you and actually highlight the value of this attribute if you misspell it, or even navigate you to the connected field with a somethihg+click on it, but still, I'd argue that calling a Java bean field by it's name in a String is quite a poor (yet inevitable) implementation of "binding" the bidirectional association together.

        But wait! "mappedBy"?! Seems like we have another fallacy here. The word "mapping" is surely from another story. What mapping is this all about? We haven't said a word yet about the mapping of the entities into a relational data source, all we did was modeling the domain. But let's blame this poor choice of the attribute name (and breaking the separation of concerns) on the developers of the JPA standard.

        Another weirdness here is the "fetch" attribute that every association annotation has. "Fetch" is actually a concept of the data source query optimization that allows us to lazily load some heavily packed associations that may not be always of use. For instance, if we only want to show the User's name, why should the data source fetch the collection of roles for us? That's where the "fetch" attribute comes in:
public class User {

  private String name;

  @ManyToMany(fetch = FetchType.LAZY)
  private Set<Role> roles;

}
        But wait, we find ourselves even more into the data source and mapping concerns here. Why do those "fetch" types even matter if all we want for now is to simply model the domain?

        To conclude, I believe that these four annotations — @OneToOne, @OneToMany, @ManyToOne and @ManyToMany — are intended for the developer to model the domain, and they surely would be better off in another package or even another API that has nothing to do with "persistence". Maybe even somewhere in Java SE. But we have them only in Enterprise Edition — as if domain modeling has to be done only in Enterprise, and only in connection with the underlying relational data sources. But that's not always the case. Those annotations would be useful in single-user, desktop applications as well, or even in applications that have no persistence whatsoever but still need to have a domain model. And those annotations are no good place to specify fetching attributes, either.