Ola Bini talks about a layered architecture, with a stable, typesafe, performant kernel, providing the foundation for a malleable DSL-based end-user API. To me this makes a lot of sense, at least in the context of the large codebase of existing Java applications. Language adoption in the mainstream has traditionally been evolutionary and for quite some time we will see lots of layers and wrappers being written and exposed as flexible APIs in the newer languages, over today's mainstream codebases acting as the so-called kernel.
Java is still the most dominant of these forces in application development, particularly in the enterprise segment. And given the degree of penetration that Java, the language, has enjoyed over the last decade or so, coupled with the strong ecosystem that it has been able to create, it will possibly remain the most potent candidate for what Ola Bini calls the kernel layer.
With more and more mindset converging towards new languages on the JVM, albeit in blogs or toy applications, I am sure many of them have already started thinking about polyglot programming paradigms. In an existing application, with the basic abstractions implemented in Java, can we use one of its newer cousins to design more dynamic APIs ? With the toolsets and libraries still in nascent state of evolution, it will be quite some time (if ever at all) that some BigCo decides to develop a complete enterprise application in JRuby or Scala or Groovy. After all, the BigCos are least excited with Ruby evals, Groovy builders or Scala type inferencing. It is the ecosystem that matters to them, it is the availability of programmers that counts for them and it is the comfort factor of their IT managers that calls the shots and decides on the development platform.
Anyway, this is not an enterprise application. I tried to build a layer of Scala APIs on top of some of the utilitarian Java classes in a medium sized application. It was fun, but more importantly, it showed how better APIs evolve easily once you can work with more powerful interoperable languages on a common runtime.
Consider this simplified example Java class, which has worked so faithfully for my client over the last couple of years ..
// Account.java
public class Account {
private List<String> names;
private String number;
private List<Address> addresses;
private BigDecimal interest;
private Status status;
private AccountType accountType;
public Account(String name, String number, Address address) {
//..
}
//.. standard getters
public void calculate(BigDecimal precision, Calculator c) {
interest = c.calculate(this, precision);
}
public boolean isOpen() {
return status.equals(Status.OPEN);
}
}
Nothing complex, usual verbose stuff, encapsulating the domain model that our client has been using for years ..
I thought it may be time for a facelift with some smarter APIs for the clients of this class, keeping the core logic untouched.
Let us have a Scala class which will act as an adapter to the existing Java class. Later we will find out how some of the magic of Scala *implicits* enables us to use the adaptee seamlessly with the adaptor.
// scala class: RichAccount.scala
// takes the Java object to construct the rich Scala object
class RichAccount(value: Account) {
//..
}
Scala collections are much richer than Java ones - hence it makes sense to expose the collection members as Scala Lists. Later we will find out how the client can use these richer data structures to cook up some more dynamic functionalities.
class RichAccount(value: Account) {
//..
def names =
(new BufferWrapper[String] {
def underlying = value.getNames()
}).toList
def addresses =
(new BufferWrapper[Address] {
def underlying = value.getAddresses()
}).toList
def interests =
(new BufferWrapper[java.math.BigDecimal] {
def underlying = value.getInterests()
}).toList
}
Now with the properties exposed as Scala lists, we can cook up some more elegant APIs, using the added power of Scala collections and comprehensions.
class RichAccount(value: Account) {
//..as above
// check if the account belongs to a particular name
def belongsTo(name: String): boolean = {
names exists (s => s == name)
}
}
Then the client code can use these APIs as higher order functions ..
// filter out all accounts belonging to debasish
accounts filter(_ belongsTo "debasish") foreach(a => println(a.getName()))
or for a functional variant of computing the sum of all non-zero accrued interest over all accounts ..
accounts.filter(_ belongsTo "debasish")
.map(_.calculateInterest(java.math.BigDecimal.valueOf(0.25)))
.filter(_ != 0)
.foldLeft(java.math.BigDecimal.ZERO)(_.add(_))
This style of programming, despite having chaining of function calls are very much intuitive to the reader (expression oriented programming), since it represents the way we think about the computation within our mind. I am sure your clients will love it and the best part is that, you still have your tried-and-tested Java objects doing all the heavy-lifting at the backend.
Similarly, we can write some friendly methods in the Scala class that act as builder APIs ..
class RichAccount(value: Account) {
//..as above
def <<(address: Address) = {
value.addAddress(address)
this
}
def <<(name: String) = {
value.addName(name)
this
}
}
and which allows client code of the following form to build up the name and address list of an account ..
acc << "shubhasis"
<< new Address(13, "street_1n", "700098")
<< "ashis"
Scala Implicits for more concise API
Now that we have some of the above APIs, how can we ensure that the Scala class really serves as a seamless extension (or adaptation) of the Java class. The answer is the *implicit* feature of Scala language. Implicits offer seamless conversions between types and makes it easy to extend third party libraries and frameworks. Martin Oderskey has a nice writeup on this feature in his blog. In this example we use the same feature to provide an implicit conversion function from the Java class to the Scala class ..
implicit def enrichAccount(acc: Account): RichAccount =
new RichAccount(acc)
This defines the implicit conversion function which the complier transparently uses to convert
Account
to RichAccount
. So the client can now write ..// instantiate using Java class
val myAccount = new Account("debasish", "100", new Address(12, "street_1", "700097"))
and watch (or feel) the compiler transparently converting the Java instance to a Scala instance. He can now use all the rich APIs that the Scala class has cooked up for him ..
// use the functional API of Scala on this object
myAccount.names.reverse foreach(println)
We can also use Scala implicits more effectively and more idiomatically to make our APIs smarter and extensible. Have a look at the Java API for calculation of interest in the Java class Account :
public void calculate(BigDecimal precision, Calculator c) {
interest = c.calculate(this, precision);
}
Here
Calculator
is a Java interface, whose implementation will possibly be injected by a DI container like Spring or Guice. We can make this a smarter API in Scala and possibly do away with the requirement of using a DI container.class RichAccount(value: Account) {
//..as above
def calculateInterest(precision: java.math.BigDecimal)
(implicit calc: Calculator): java.math.BigDecimal = {
value.calculate(precision, calc)
value.getInterest()
}
}
Here we use the nice curry syntax of Scala, so that the client code can look nice, concise and intuitive ..
val accounts = List[Account](..)
accounts filter(_ belongsTo "debasish")
foreach(a => println(a.calculateInterest(java.math.BigDecimal.valueOf(0.25))))
Note that the
Calculator
parameter in calculateInterest()
has been declared implicit. Hence we can optionally do away specifying it explicitly so long we have an implicit definition provided. The client code has to mention the following declaration, some place accessible to his APIs ..implicit val calc = new DefaultCalculator
and we need no DI magic for this. It's all part of the language.
Spice up the class with some control abstractions
Finally we can use higher order functions of Scala to define some nice control abstractions for your Java objects ..
class RichAccount(value: Account) {
//..as above
def withAccount(accountType: AccountType)(operation: => Unit) = {
if (!value.isOpen())
throw new Exception("account not open")
// other validations
if (value.getAccountType().equals(accountType))
operation
}
}
}
The method is an equivalent of the Template Method pattern of OO languages. But using functional Scala, we have been able to templatize the variable part of the algorithm as a higher order function without the additional complexity of creating one more subclass. The client code can be as intuitive as ..
a1.withAccount(AccountType.SAVINGS) {
println(a1.calculateInterest(java.math.BigDecimal.valueOf(0.25)))
}
As I mentioned in the beginning, I think this layering approach is going to be the normal course of evolution in mainstream adoption of other JVM languages. Java is so dominant today, not without reason. Java commands the most dominant ecosystem today - the toolsets, libraries, IDEs and the much familiar curly brace syntax has contributed to the terrific growth of Java as a language. And this domination of the Java language has also led to the evolution of Java as a platform. Smalltalk may have been a great platform, but it lost out mainly because developers didn't accept Smalltalk as a mainstream programming language. Now that we have seen the evolution of so many JVM languages, it is about time we experiment with polyglot paradigms and find our way to the next big language set (yes, it need not be a single language) of application development. So long we have been using Java as the be-all and end-all language in application development. Now that we have options, we can think about using the better features of other JVM languages to offer a smarter interface to our clients.
4 comments:
Interesting stuff, Debasish, but I have one request: could you use a different way to post code snippets?
The way you're doing it right now forces readers to scroll horizontally back and forth to read your listings, and it's very annoying...
Thanks!
--
Cedric
Awesome post! I'm a longtime java developer who works professionaly right now as a C# developer. These "research" languages F# and Scala have me fascinated, and I look forward to increasing my productivity with them.
Really cool stuff.
Posts like this one will, hopefully, be helpful to transition in a smooth way from Java to languages like Scala that can be looked at as 'layered on top of Java'.
And all this without loosing the Java EcoSystem (as you name it).
Thanks so much for this post!
Thanks Cedric for the reminder. This was long overdue. I have now made it wider, so that code snippets can fit better.
and thanks for dropping by ..
- Debasish
Post a Comment