August 22, 2015

JSF / JEE enums best practices



Having discussed some general Java enum best practices in my previous blog post, I’d like to quickly add some enum best practices which specifically apply to a JSF / JEE environment. These stem from my own JSF experience.

When to use enums

In a JSF (or, more generally speaking, in a JEE) environment, you would typically use enums less often than in a simple Java SE application because in JEE, you generally want to keep some sort of runtime flexibility, which is exactly what we try to diminish when using enums, as shown in the previous article.

With enums, you have a set of values fixed at compile time. In a JEE environment, however, you typically want to be able to alter any set of values at runtime with CRUD operations. This enables you to extend the application without having to re-compile it, but rather add some DB entries.

Of course, you may also keep in mind that typically, creating and maintaining a full-fletched CRUD entity including classes, services, DB tables and DB entries means more effort than just creating an enum.

Here are some typical examples of when to use enums and when not:
  • Countries, states: Don’t use enums. Yes, political structures don’t last forever. Instead, you want to have a Country / State class and a set of predefined entities in a DB an application admin can alter using CRUD.
  • Some kind of category / tag like “genre” for books: Typical DB table use case.
  • Some “is-a-kind-of…” description like “type” of book (e.g. normal book, audio book, e-book): You wouldn’t use enums or a String value from a DB table either. Instead, use inheritance with an abstract Book as the parent class, and concrete subclasses. Use an appropriate O/RM mapping (e.g. JPA’s InheritanceType.JOINED) to map inheritance to the DB. This approach fits an object-oriented architecture most closely.
  • Gender (male / female / undefined): A typical use case for enums. You’ll never have to alter the predefined value set so make it compile time safe.
  • Payment type (credit card / invoice / PayPal,…): You may use enums here as you may expect that in order to alter the set of options, you’ll have to re-program parts of your application anyway.
  • Currency: A border case. If it’s just very generic like $, £, €, ¥, you could use enums as you might expect these currencies to outlive your application. If however you want to provide an exhaustive list of currencies, treat it just like geopolitical entities and keep them in a CRUD table. I would highly recommend this DB approach here.

How to use enums in JPA

If you use an enum, it will eventually just be a property of a bean and as such, will be mapped to the DB if the entity is.

JPA allows for two strategies here, determined by the value used with the @Enumerated annotation on the property in question:
  • @Enumerated(EnumType.ORDINAL): Saves the value’s #ordinal() to the DB (this is the default if value is omitted).
  • @Enumerated(EnumType.STRING): Saves the value’s #name() to the DB.
It’s critical that you don’t go with the default, but use STRING:
@Enumerated(EnumType.STRING)
private PaymentType paymentType;
In fact, using ORDINAL would make your O/R mapping extremely brittle as adding a new enum value somewhere in between existing values would mix up subsequent values, thus breaking the O/R mapping. On the other hand, of course, you never want to change an enum value’s #name() unless you really want it exchanged.

Removing an enum value of course requires a cleanup of existing DB data before bringing the application back online.

How to use enums in JSF

When it comes to enums, the excellent general-purpose JSF util library OmniFaces once again proves itself useful. Combining its functionality with standard JSF best practices, working with enums becomes really easy and straighforward.

The most typical use case for enums in a JSF application which I will cover here is to show all available enum values in a <select…> component, allowing the user to select one and set it as a model property. Here is the code required for that:
<o:importFunctions type="ch.codebulb.jsfenumbestpractices.model.PaymentType" />
...
<h:selectOneMenu id="paymentType" value="#{paymentController.entity.paymentType}">
    <f:selectItems var="item" value="#{PaymentType:values()}" 
                   itemLabel="#{msg['model.paymentType.'.concat(item)]}" />
</h:selectOneMenu>
  • <o:importFunctions> registers a class’s static methods as EL functions in the current page. In the example, we use this to access the PaymentType enum’s values() method with returns a PaymentType[].
  • We set the itemLabel to an I18N key registered in a messages.properties file. Note that you have to use String#concat() instead of + to concat Strings in EL 2.2+.
By using the standard I18N facilities, we can easily provide localization for enum values. Here’s an example messages.properties file:
model.paymentType.INVOICE=Invoice
model.paymentType.CREDIT_CARD=Credit card
model.paymentType.PAYPAL=Pay Pal
For the special case of a <seclectMany…> component, using the omnifaces.GenericEnumConverter additionally will make sure type information is preserved. See its documentation for more information.

Conclusion

That’s all there is to say about using enums in a JSF / JEE environment. By relying on a mature tech stack, enums aren’t anything special anymore; the actual critical aspect is their proper usage as determined by the application design.

If you feel like this article still lacks any important enum-related information, please let me know in the comments section. Other than that, this will probably me my last word on enum usage best practices for a long time.

No comments:

Post a Comment