River Definition Language – Motivation and Key Ideas
So you’ve seen the demos, attended the sessions and want to find out more about the buzz. It’s probably best to start with a general getting started guide and getting some hands-on experience with the coding itself.
This blog post, however, is aimed at those who want to get a glimpse of the broader context, what we’re trying to achieve and some of the design choices made.
In this post, I’ll try to provide a high level overview of the language itself, and provide some insight into the underlying principles we’re following. Later we’ll expand more on specific features and behaviors.
Our primary goal with the River development model (and language – RDL) is to significantly improve the development experience of business software applications. This includes writing the application code, testing it and evolving/maintaining it over time. We also target various development scenarios with a range of variables: on-demand/on premise, one-off projects vs. products, mobile/web, extensions of core, etc.
How we achieve this improvement can be a subject to a long (and bitter?) debate, which we won’t go into here. We chose to focus on several elements, essentially embodying the spirit of Timeless Software Architecture , as already outlined in the past by Dr. Vishal Sikka, SAP’s CTO.
In order to achieve that goal, we primarily target the complexity of heterogeneous technology landscape when building a business application: ideally, a developer should focus on the problem domain he’s trying to solve with software; realistically this is not the case today. The myriad of technologies, languages and programming models make the development of even a moderately complex application a daunting task that requires considerable mapping between the different technologies, and often results in an extensive bill of materials – not because the application is inherently complex, but because it is implemented using different components and technologies. This is further compounded when you take into consideration non-functional requirements such as optimization, maintainability, security, etc.
The HANA platform has already simplified part of it by providing a comprehensive platform on top of the DB engine. But we intend to go even a step further in order to minimize the complexity a developer has to manage when developing a solution. River aims to allow an application developer to concentrate on the logical domain of his solution, rather than realizing it technically. Essentially distilling the intent of the solution; decoupling it from runtime containers used to execute it. The same principal follows also into the application assembly process itself. The code, and how it’s structured, should reflect the logical business intent as much as possible, and not the technical assembly and dependencies between the different source artifacts.
Given this motivation we adopt a “specification” mindset rather than a “programming” mindset: a developer should specify what his application does, not necessarily program the system on which it runs. There is of course a fine line between purity of concept and practicality, and sometimes trade-offs are made. This is not a hard rule but rather a guiding design principle to keep in mind when looking and examining RDL.
Complexity is manifested in other aspects, not just the code itself. Understanding an application that is written in different technologies and syntax variants is hard, but also building, testing and debugging it. We intend to tackle some of these issues as well, with a combination of a simple logical model for describing the application, as well as the tools supporting it. Collaboration between team members working on the project is also a sore issue – specifying the application and coding it are usually two different activities, done by people with different skill sets and background. Facilitating a better communication between different team members is also one of the goals we have in front of us, though it’s rather far-fetched at the moment.
Key Design Principles
Having outlined the goals, we set ourselves to the task of trying to come up with a development model that will indeed try to answer these needs. Below I highlight the major design principles we try to follow when evolving the model, language and tools.
The reasoning here is twofold:
- We want to avoid creating yet another runtime container, a “virtual machine”, that will only complicate the technical setup of the system, its integration, and eventually its cost of ownership.
- Having another virtual machine created and deployed could easily prove to be counter-productive when it comes to runtime performance as well. It will most probably lead to unnecessary data conversions and data transfer.
Generally, we’re not in shortage of efficient runtime containers, what we miss is how to develop content for these effectively. Especially in an application that requires the integration of more than one container.
The abstraction from the separate runtime containers (how the application runs) is defined in the language and contained in the RDL compiler. Specifically, the distribution between different runtime containers is not defined in the application code.
This principle – “Elastic Execution” – is important for enabling the flexibility and allowing an application to evolve at a different pace from its underlying runtime technology (-ies).
While the principles outlined here aren’t HANA-specific, it so happens that the HANA platform provides a wonderful use-case and proof point for realizing these principles. The HANA platform itself already includes an impressive number of different runtime containers and their accompanying artifacts. Applying this model over HANA not only helps with developing content over the HANA platform, it also serves as an interestingvalidation of this model for us.
Realizing that we spend most of our time reading application code rather than writing it, it is extremely important that the way for specifying business scenario will be simple to read and follow. So we try to make sure that River application code is concise, expressive and readable. As a rule of thumb, an application developer should not be bothered with the technicalities of deploying the code, defining and managing runtime dependencies between different runtime artifacts, parallelization of running code, etc.
A developer should have the tools and environment to quickly get a grasp of what the different application parts do, how they relate to each other, and the logical integration between the different application components.
We intentionally shy away from expressing technical computing operations and optimization techniques. For example, you will not see any notion of memory allocation or memory resident values vs. persistent ones. It is the sort of low level detail that should not bother an application developer working to solve a business problem.
This is not to say that you will never see any shred of technical configuration detail in River code – you will. But it should be the rare case and in any case not something that is fundamental to understanding the application and relevant business scenario.
Coherency End to End
If we want to provide a model that indeed rids developers of the need to worry about the technicalities of deploying the code, managing the runtime dependencies between different components and a myriad of configuration resources, we need to provide a model where the application is specified in a simple and coherent model and language.
It is more than just providing a nicer looking syntax – it is about specifying the different aspects of the application in a given environment and allowing the developer to follow the logical connections between them. This model should encompass at least all the major building blocks of a business application, and do so in a way that is easy to follow and understand.
The River language supports specifying an application’s data model, its business rules, access control, validations and error handling as well as componentizing it all and allowing clear abstractions. All these aspects are specified in syntax that allows combining them in a single resource, with an underlying model that allows their integration (and automatic checking).
In today’s technology landscape, applications are rarely built using one technology. While most developers like to “start fresh” when coding an application, the reality is that there are considerably large, well established, code bases out there with years of work put into them. Even new applications will most likely be built on top of these.
Additionally, it is very likely that a developer would encounter the need to leverage some technology-specific runtime capability that isn’t easily expressed in River or supported by the River compiler.
To this end, we adopt an openness principle: River allows the consumption of components created using other technologies, or “breaking out” from River code to other technologies. The challenge is of course to allow this consumption without compromising other aspects mentioned above, namely simplicity and coherency, but also River’s ability to optimize the running code – invoking a runtime-specific code binds the execution to that runtime container.
We still strive to keep the application code itself clean, and allow decoupling of the application code (the content) from its runtime container. This isn’t necessarily an easy task always, especially when trying to automate it. The tradeoff is therefore sometimes left to the developer coding the application – coding in a specific technology adds to the code complexity as well as binds the execution to a specific runtime container. But it is sometimes necessary, or even preferable when trying to achieve optimal performance, or leveraging some language-specific capability.
So far I’ve covered just the “big picture” – the motivation and key design principles behind the River Definition Language. Next we’ll dive into more concrete examples, and the underlying model.