In this paper, the point
- Reladomo is an enterprise-class Java ORM developed by Goldman Sachs and released as an open source project in 2016.
- Reladomo offers a number of unique and interesting features, including a strongly typed query language, data sharding, temporary table support, true testability, and high-performance caching.
- Reladomo is developed around a set of core values and is therefore an “Opinionated” framework.
- This article provides examples that nicely illustrate the usability and programmability of Reladomo.
Back in 2004, we faced a difficult challenge. Our Java application needs a way to abstract database interactions that no existing framework can accommodate. The application has the following requirements that go beyond what a typical solution can provide:
- The data is highly syncopated. The data is stored in more than one hundred databases with the same schema but different data.
- The data is Bitemporal. This content will be introduced in the second part of this article, please pay attention!
- Queries on data are not completely static, and some queries must be created dynamically from a series of complex user inputs.
- The data model has a degree of complexity, with hundreds of database tables.
We have been working on Reladomo since 2004 and completed our first production deployment that year. Since then, we have regularly released Reladomo versions. Reladomo has been widely adopted at Goldman Sachs over the years, with major new features added under the guidance of the app. Reladomo is used in dozens of applications, including multiple ledger, Middle Office transaction processing, Balance Sheet processing and more. In 2016, Goldman Sachs released Reladomo open source under the Apache 2.0 license. Reladomo is an acronym for RELAtional DOMain Objects.
Why would we build another ORM product?
The reason was simply that the existing solutions did not meet our core requirements, and there were some unresolved issues with traditional ORM.
We decided to eliminate boilerplates and incomplete structures at the code level. Does not exist in the Reladomo for connection to obtain leakage, close, or empty, there was also no Session, EntityManager and LazyInitializationException. Reladomo provides the API in two basic ways, on the domain objects themselves, and with height enhancement through strongly typed lists.
Another key point for us was Reladomo’s query language. In general, string-based languages are completely inappropriate for our applications and object-oriented code. Building dynamic queries by concatenating strings together doesn’t work well for all but the most trivial queries. Maintaining these dynamic queries based on string concatenations is undoubtedly a headache.
In addition, we need full native support for data sharding. Data sharding in Reladomo is very flexible and can handle the same primary key value that appears in different shards and points to different objects. The syntax of sharded queries is a natural fit for query languages.
As pointed out in Developing Time-oriented Database Applications in SQL written by Richard Snodgrass, Support for timing (single or double timing), which helps database designers record and reason about timing information, is a feature entirely unique to Reladomo. This feature applies to a wide range of applications, from a wide variety of billing systems to Reference Data for all types of Data that require full reproducibility. Even basic applications like project collaboration tools can benefit from a single sequential representation, becoming a kind of time machine that allows the user interface to show how things change.
True testability is a very important thing for an application to work properly. We decided early on that self-reliance was the only way to do things right. Most Reladomo tests are written using Reladomo’s own testing tools! We have a very pragmatic view of testing, which is to add long-term value to testing. Reladomo tests are easy to set up, execute all production code on an in-memory database, and allow continuous integration testing. These tests help developers understand how to interact with the database without having to configure a development environment with the database installed.
Finally, we don’t want to compromise on performance. Caching is one of the most important factors affecting performance. From a technical perspective, caching is the most complex part of Reladomo. Reladomo’s cache is a keyless, multi-index transaction object cache. Objects are cached as objects themselves, ensuring that the data in the object occupies a single memory reference. The object cache also attaches a query cache to the same object. The query cache is intelligent and does not return expired results. Caching works correctly when multiple JVMS write the same data using Reladomo. The cache can be configured to be allocated on demand at startup or fully maximized. Objects can even be stored off-heap if the right type of data and applications have a large, reproducible cache. In our production system, we ran over 200GB of cache.
Development principles
Reladomo is positioned as a framework, not a software library. To determine whether a programming pattern works, a framework is more formal and has its own set of methods than a software library. Reladomo also generates code, and the API it generates is understood to be freely usable in the rest of the developer’s code. It is therefore imperative that the framework code and application code have a uniform look and feel for the code to work well.
We have defined the following core values so that our potential users can determine if Reladomo is appropriate:
- Code is designed to run in production for years or even decades.
- Once and only once (DRY, Don’t repeat yourself).
- Build code that is easy to change.
- Write code in an approach to dome-based objects.
- Don’t compromise on correctness and consistency.
We detail these core values and their impact in our Concept and Vision document.
Usability and programmability
To demonstrate some of Reladomo’s features, we will use two small domain models. Firstly, a non-temporal Model about pets is given:
(Click to enlarge image)
The second model is a ledger model that appears in textbooks:
(Click to enlarge image)
In this model, Account objects trade securities (Product objects), and a Product object has multiple identifiers (also known as synonyms). The Balance object holds accumulated Balance information. Balance can represent any number of cumulative values in an Account, such as total, taxable income, interest, and so on. The code for the above model is available on GitHub.
As we will see below, this example is a bi-temporal model. From now on, we will ignore the temporal bits, which will not affect the feature presentation.
When defining the model, we created a Relamodo object for each conceptual object and used the created object to generate some classes. We expect each developer to define a domain class that can be used in a real business domain. The generated object will never overwrite the actual class in the domain. The abstract superclasses of these classes are generated each time the model or Reladomo version is changed. Developers can also add methods to these concrete classes and check in changes to the version control system.
Most of the apis provided by Reladomo are in the generated classes. In our pet example, such methods include: PetFinder, PetAbstract, and PetListAbstract. PetFinder has normal GET /set methods, as well as other methods for persistence. The most interesting parts of the API are the Finder and List.
As indicated by method names, class-specific Finder methods (such as PersonFinder) are used to find things. Here’s a simple example:
__Mon Aug 21 2017 15:02:22 GMT+0800 (CST)____Mon Aug 21 2017 15:02:22 GMT+0800 (CST)__Person john = PersonFinder.findOne(PersonFinder.personId().eq(8)); __Mon Aug 21 2017 15:02:22 GMT+0800 (CST)____Mon Aug 21 2017 15:02:22 GMT+0800 (CST)__Copy the code
Note that there are no connections or sessions to get or close. The objects retrieved by the method are valid references in any scenario. The developer can pass the object to different threads and participate in the unit of work of the transaction. If the method returns more than one object, findOne throws an exception.
So if we decompose this expression. Where personFinder.firstName () is an attribute that is typed, that is, StringAttribute. Developers can call firstName().eq(“John”) instead of firstName().eq(8) or firstName().eq(someDate). This object also has some special methods that are not seen in other types of properties, such as:
__Mon Aug 21 2017 15:02:22 GMT+0800 (CST)____Mon Aug 21 2017 15:02:22 GMT+0800 (CST)__PersonFinder.firstName().toLowerCase().startsWith("j")__Mon Aug 21 2017 15:02:22 GMT+0800 (CST)____Mon Aug 21 2017 15:02:22 GMT+0800 (CST)__Copy the code
Note that methods like toLowerCase(), startsWith(), and so on are not available on what we call IntegerAttribute, which has its own set of special methods.
The above points out two key points of usability. First, the IDE you use should help developers write the right code. Second, after the developer changes the model, the compiler should be able to find all the changes that need to be made.
Property has methods to create operations on itself, such as eq(), greaterThan(), and so on. In Reladomo, actions are used to retrieve objects through methods such as finder.findone or finder.findmany. The implementation of the operation is immutable and can be used in combination with and() and or(). Such as:
__Mon Aug 21 2017 15:02:22 GMT+0800 (CST)____Mon Aug 21 2017 15:02:22 GMT+0800 (CST)__Operation op = PersonFinder.firstName().eq("John"); op = op.and(PersonFinder.lastName().endsWith("e")); PersonList johns = PersonFinder.findMany(op); __Mon Aug 21 2017 15:02:22 GMT+0800 (CST)____Mon Aug 21 2017 15:02:22 GMT+0800 (CST)__Copy the code
For applications with a large number of I/OS, load data in batches. This can be done using the IN statement. For example, if we build the following operation:
__Mon Aug 21 2017 15:02:22 GMT+0800 (CST)____Mon Aug 21 2017 15:02:22 GMT+0800 (CST)__Set<String> lastNames = ... //lastNames is defined as a large collection. For example, it contains 10,000 elements. PersonList largeList = PersonFinder.findMany(PersonFinder.lastName().in(lastNames)); __Mon Aug 21 2017 15:02:22 GMT+0800 (CST)____Mon Aug 21 2017 15:02:22 GMT+0800 (CST)__Copy the code
Reladomo will analyze the operations in the background and generate the corresponding SQL statements. So what kind of SQL statement does a large IN statement produce? For Reladomo, the answer is: “It depends”. Reladomo can choose to publish multiple IN statements, or it can choose to use a temporary table join that depends on the target database. The selection is transparent to the user. Reladomo’s implementation effectively returns the correct results depending on the operation and database, without the developer having to make a choice. Because if the configuration changes, or if you have to write complex code to deal with variability, you’re going to make the wrong choice. Reladomo comes with full functionality!
A primary key
The primary key in Reladomo is any combination of object attributes. There is no need to define key classes or otherwise handle these attributes. Our philosophy is that composite keys are undoubtedly Natural keys in all models, and there should be no barriers to their use. In our example of the basic transaction model, the ProductSynonym class has natural composite keys:
__Mon Aug 21 2017 15:02:22 GMT+0800 (CST)____Mon Aug 21 2017 15:02:22 GMT+0800 (CST)__<Attribute name="productId"
javaType="int"
columnName="PRODUCT_ID"
primaryKey="true"/>
<Attribute name="synonymType"
javaType="String"
columnName="SYNONYM_TYPE"
primaryKey="true"/>__Mon Aug 21 2017 15:02:22 GMT+0800 (CST)____Mon Aug 21 2017 15:02:22 GMT+0800 (CST)__Copy the code
Of course, composite keys are also useful in some scenarios. We use a high-performance table-based approach that supports the generation of composite keys. Synthesis key generation is batch, asynchronous, and on demand.
Relationship between
The relationships between classes are defined in the model:
__Mon Aug 21 2017 15:02:22 GMT+0800 (CST)____Mon Aug 21 2017 15:02:22 GMT+0800 (CST)__<Relationship name="pets" relatedObject="Pet" cardinality="one-to-many" relatedIsDependent="true" reverseRelationshipName="owner"> this.personId = Pet.personId </Relationship>__Mon Aug 21 2017 15:02:22 GMT+0800 (CST)____Mon Aug 21 2017 15:02:22 GMT+0800 (CST)__Copy the code
Definition relationships provide three reading capabilities:
- The get method on the object. If the relationship is identified as bidirectional by the reverseRelationshipName attribute, then the associated object may also have a GET method. For example, person.getpets ().
- Browse for relationships onFinder, for example, personFinder.pets ().
- The ability to drill down on relationships on a per-query basis.
Deep acquisition is the ability to efficiently retrieve related objects, avoiding the famous “N+1 query problem”. If we want to retrieve the Person object, we can request that the Pet object be efficiently loaded as follows:
__Mon Aug 21 2017 15:02:22 GMT+0800 (CST)____Mon Aug 21 2017 15:02:22 GMT+0800 (CST)__PersonList people = ... people.deepFetch(PersonFinder.pets()); __Mon Aug 21 2017 15:02:22 GMT+0800 (CST)____Mon Aug 21 2017 15:02:22 GMT+0800 (CST)__Copy the code
Or implemented as a more interesting example:
__Mon Aug 21 2017 15:02:22 GMT+0800 (CST)____Mon Aug 21 2017 15:02:22 GMT+0800 (CST)__TradeList trades = ... trades.deepFetch(TradeFinder.account()); // Get the account information for these trades. trades.deepFetch(TradeFinder.product() .cusipSynonym()); // Get product, and a CUSIP synonym of "product" for "trades" (which is a type of identifier) trades.deepfetch (tradefinder.product ().synonymbytype ("ISN")); // Again, get the ISN of product (another identifier). __Mon Aug 21 2017 15:02:22 GMT+0800 (CST)____Mon Aug 21 2017 15:02:22 GMT+0800 (CST)__Copy the code
Any accessible part of the diagram can be specified. Notice how the approach used here avoids being implemented as part of the model. Models do not have the concept of “eager” or “lazy,” but are implemented using specific parts of code. Thus, changing the model is unlikely to significantly improve the IO and performance of the existing code, thereby making the model more agile.
Relationships can be used when creating operations. Such as:
__Mon Aug 21 2017 15:02:22 GMT+0800 (CST)____Mon Aug 21 2017 15:02:22 GMT+0800 (CST)__Operation op = TradeFinder .account() .location() .eq("NY"); // Find all trades belonging to the account named "NY". op = op.and(TradeFinder.product() .productName() .in(productNames)); // And the product name is included in the TradeList given. TradeList trades2 = TradeFinder.findMany(op); __Mon Aug 21 2017 15:02:22 GMT+0800 (CST)____Mon Aug 21 2017 15:02:22 GMT+0800 (CST)__Copy the code
In Reladomo, the relationship implementation does not have an actual reference. This makes adding relationships costless from a memory and IO perspective.
In Reladomo, relationships are very flexible. Returning to the example, the Product object has several synonyms of different types (for example, CUSIP, Ticket, and so on). We have defined this example in the Trade model. The traditional one-to-many relationship between a Product and a ProductSynonym hardly works:
__Mon Aug 21 2017 15:02:23 GMT+0800 (CST)____Mon Aug 21 2017 15:02:22 GMT+0800 (CST)__<Relationship name="synonyms" relatedObject="ProductSynonym" cardinality="one-to-many"> this.productId = ProductSynonym.productId </Relationship>__Mon Aug 21 2017 15:02:22 GMT+0800 (CST)____Mon Aug 21 2017 15:02:23 GMT+0800 (CST)__Copy the code
This is because it is rare for a user query to return all synonyms of Product. There are two types of high-level relationships that make this generic example more useful. One is a constant expression that allows important business concepts to be represented in the model. For example, if we want to access a CUSIP synonym of Product by name, we add the following relation:
__Mon Aug 21 2017 15:02:23 GMT+0800 (CST)____Mon Aug 21 2017 15:02:23 GMT+0800 (CST)__<Relationship name="cusipSynonym"
relatedObject="ProductSynonym"
cardinality="one-to-one">
this.productId = ProductSynonym.productId and
ProductSynonym.synonymType = "CUS"
</Relationship>__Mon Aug 21 2017 15:02:23 GMT+0800 (CST)____Mon Aug 21 2017 15:02:23 GMT+0800 (CST)__Copy the code
Notice how we use the cusipm relationship in deepFetch and query in the example given above. This approach has three advantages: first, we don’t have to repeat “CUS” throughout the code; Second, if we want CUSIP, we don’t have to pay the IO price to retrieve all the synonyms; Finally, queries are more readable and written more conventionally.
composability
One of the biggest problems with string-based queries is that they are very difficult to compose. We take composability to a new level with a type-safe query language for dome-based objects. To illustrate this concept, let’s look at an interesting example.
For our Trade model, both the Trade object and Balance object have the same relationship as Account and Product. Suppose you have a GUI that allows Trade to be retrieved by filtering Account and Product information, and a window that provides Balance to be retrieved by filtering Account and Product information. Since you’re dealing with the same entity, the filters are naturally the same. With Reladomo, it’s easy to share code between the two. We have abstracted the business logic for Product and Account into multiple GUI component classes, which we can use as follows:
__Mon Aug 21 2017 15:02:23 GMT+0800 (CST)____Mon Aug 21 2017 15:02:23 GMT+0800 (CST)__public BalanceList retrieveBalances()
{
Operation op = BalanceFinder.businessDate().eq(readUserDate());
op = op.and(BalanceFinder.desk().in(readUserDesks()));
Operation refDataOp = accountComponent.getUserOperation(
BalanceFinder.account());
refDataOp = refDataOp.and(
productComponent.getUserOperation(BalanceFinder.product()));
op = op.and(refDataOp);
return BalanceFinder.findMany(op);
}__Mon Aug 21 2017 15:02:23 GMT+0800 (CST)____Mon Aug 21 2017 15:02:23 GMT+0800 (CST)__Copy the code
The above code will publish the following SQL statement:
__Mon Aug 21 2017 15:02:23 GMT+0800 (CST)____Mon Aug 21 2017 15:02:23 GMT+0800 (CST)__select t0.ACCT_ID,t0.PRODUCT_ID,t0.BALANCE_TYPE,t0.VALUE,t0.FROM_Z, t0.THRU_Z,t0.IN_Z,t0.OUT_Z from BALANCE t0 inner join PRODUCT t1 on t0.PRODUCT_ID = t1.PRODUCT_ID inner join PRODUCT_SYNONYM t2 on t1.PRODUCT_ID = t2.PRODUCT_ID inner join ACCOUNT t3 on t0.ACCT_ID = t3.ACCT_ID where t1.FROM_Z <= THRU_Z > '2017-03-02 00:00:00.000' and t1.out_z = '9999-12-01 23:59:00.000' and THRU_Z > '2017-03-02 00:00:00.000' and t1.thru_z > '2017-03-02 00:00:00.000' and t1.out_z = '9999-12-01 23:59:00.000' and T2.out_z = '9999-12-01 23:59:00.000' and t2.from_z <= '2017-03-02 00:00:00.000' and t2.thru_z > '2017-03-02 00:00.000' and t2.SYNONYM_TYPE = 'CUS' and t2.SYNONYM_VAL in ('ABC', 'XYZ') and t1.maturity_date < '2020-01-01' and t3.from_z <= '2017-03-02 00:00:00.000' and t3.thru_z > '2017-03-02 00:00:00.000' and t3.OUT_Z = '9999-12-01 23:59:00.000' and t3.CITY = 'NY' and t3. FROM_Z <= '2017-03-02 00:00:00.000' and THRU_Z > '2017-03-02 00:00:00.000' and t0.out_z = '9999-12-01 23:59:00.000' __Mon Aug 21 2017 15:02:23 GMT+0800 (CST)____Mon Aug 21 2017 15:02:23 GMT+0800 (CST)__Copy the code
For the Trade class, the ProductComponent and AccountComponen classes are fully reusable (see BalanceWindow and TradeWindow). But composability is far from that. If the business requirements change, assuming that the change is only for the Balance window and the user wants Balance to match the Account or Product filter, using Reladomo requires only one line of code to change:
__Mon Aug 21 2017 15:02:23 GMT+0800 (CST)____Mon Aug 21 2017 15:02:23 GMT+0800 (CST)__refDataOp = refDataOp.or( productComponent.getUserOperation(BalanceFinder.product())); __Mon Aug 21 2017 15:02:23 GMT+0800 (CST)____Mon Aug 21 2017 15:02:23 GMT+0800 (CST)__Copy the code
The SQL statement published today is quite different:
__Mon Aug 21 2017 15:02:23 GMT+0800 (CST)____Mon Aug 21 2017 15:02:23 GMT+0800 (CST)__select t0.ACCT_ID,t0.PRODUCT_ID,t0.BALANCE_TYPE,t0.VALUE,t0.FROM_Z, t0.THRU_Z,t0.IN_Z,t0.OUT_Z from BALANCE t0 left join ACCOUNT t1 on t0.ACCT_ID = t1.ACCT_ID and t1.OUT_Z = '9999-12-01 THRU_Z > '2017-03-02 00:00:00.000' and t1.thru_z > '2017-03-02 00:00:00.000' and t1.city = 'NY' Left join PRODUCT t2 on t0.product_id = t2.product_id and t2.from_z <= '2017-03-02 00:00:00.000' and t2.thru_z > '2017-03-02 00:00:00.000' and T2.out_z = '9999-12-01 23:59:00.000' and t2.maturity_date < '2020-01-01' left JOIN PRODUCT_SYNONYM t3 on t2.PRODUCT_ID = t3.PRODUCT_ID and t3.OUT_Z = '9999-12-01 23:59:00.000' and t3.FROM_Z <= '2017-03-02 00:00:00.000' and t3.thru_z > '2017-03-02 00:00:00.000' and t3.synonym_type = 'CUS' and t3.synonym_val in ( 'ABC', 'XYZ' ) where ( ( t1.ACCT_ID is not null ) or ( t2.PRODUCT_ID is not null and t3.PRODUCT_ID is not null ) ) and THRU_Z <= '2017-03-02 00:00:00.000' and t0.thru_z > '2017-03-02 00:00:00.000' and t0.out_z = '9999-12-01 23:59:00.000' __Mon Aug 21 2017 15:02:23 GMT+0800 (CST)____Mon Aug 21 2017 15:02:23 GMT+0800 (CST)__Copy the code
Notice the structural differences between the current SQL and the previous SQL. The requirement has changed from “and” to “OR,” and we change the code from “and” to “OR” accordingly and it works fine. There’s no doubt about it! Implementing the requirement change from “and” to “or” would be trickier if this were implemented using string-based or any query mechanism that exposed “joins.”
CRUD and Unit of Work
The Reladomo API and CRUD are implemented on objects and lists. Objects have methods like INSERT () and delete(), while lists have bulk manipulation methods but no “save” or “update” methods. The database will be updated when persistent objects are set. We expect most of the write operations to be performed in a transaction, through the command line mode:
__Mon Aug 21 2017 15:02:23 GMT+0800 (CST)____Mon Aug 21 2017 15:02:23 GMT+0800 (CST)__MithraManagerProvider.getMithraManager().executeTransactionalCommand(
tx ->
{
Person person = PersonFinder.findOne(PersonFinder.personId().eq(8));
person.setFirstName("David");
person.setLastName("Smith");
return person;
});
UPDATE PERSON
SET FIRST_NAME='David', LAST_NAME='Smith'
WHERE PERSON_ID=8__Mon Aug 21 2017 15:02:23 GMT+0800 (CST)____Mon Aug 21 2017 15:02:23 GMT+0800 (CST)__Copy the code
Database writes are grouped together and performed in batches, where the only constraint is correctness.
The PersonList object has a number of useful methods and provides a collection-based API. For example, we can do this:
__Mon Aug 21 2017 15:02:23 GMT+0800 (CST)____Mon Aug 21 2017 15:02:23 GMT+0800 (CST)__Operation op = PersonFinder.firstName().eq("John"); op = op.and(PersonFinder.lastName().endsWith("e")); PersonList johns = PersonFinder.findMany(op); johns.deleteAll(); __Mon Aug 21 2017 15:02:23 GMT+0800 (CST)____Mon Aug 21 2017 15:02:23 GMT+0800 (CST)__Copy the code
On the surface, you might think that the above statement would parse the list first and then delete the Person record in turn. Instead, the following transaction query was published:
__Mon Aug 21 2017 15:02:23 GMT+0800 (CST)____Mon Aug 21 2017 15:02:23 GMT+0800 (CST)__DELETE from PERSON WHERE LAST_NAME like '%e' AND FIRST_NAME = 'John'__Mon Aug 21 2017 15:02:23 GMT+0800 (CST)____Mon Aug 21 2017 15:02:23 GMT+0800 (CST)__Copy the code
While this is not a problem, it is not really the type of batch delete required for production applications. Given the situation where the application needs to clean up old data, it is clear that the data will not be used again, so there is no need for a complete transaction of the entire collection. The data needs to be removed in the most appropriate way, possibly using background processes. For this we can use:
__Mon Aug 21 2017 15:02:23 GMT+0800 (CST)____Mon Aug 21 2017 15:02:23 GMT+0800 (CST)__johns.deleteAllInBatches(1000); __Mon Aug 21 2017 15:02:23 GMT+0800 (CST)____Mon Aug 21 2017 15:02:23 GMT+0800 (CST)__Copy the code
Depending on the target database, the above statement will give different queries, for example:
For MS-SQL databases:
__Mon Aug 21 2017 15:02:23 GMT+0800 (CST)____Mon Aug 21 2017 15:02:23 GMT+0800 (CST)__delete top(1000) from PERSON where LAST_NAME like '%e' and FIRST_NAME = 'John'__Mon Aug 21 2017 15:02:23 GMT+0800 (CST)____Mon Aug 21 2017 15:02:23 GMT+0800 (CST)__Copy the code
For PostgreSQL databases:
__Mon Aug 21 2017 15:02:23 GMT+0800 (CST)____Mon Aug 21 2017 15:02:23 GMT+0800 (CST)__delete from PERSON
where ctid = any (array(select ctid
from PERSON
where LAST_NAME like '%e'
and FIRST_NAME = 'John'
limit 1000))__Mon Aug 21 2017 15:02:23 GMT+0800 (CST)____Mon Aug 21 2017 15:02:23 GMT+0800 (CST)__Copy the code
Reladomo will try to finish the job, deal with temporary failures and return after completing all tasks. This is what we call “Batteries Included,” which integrates common patterns and simplifies implementation.
Ease of integration
We built Reladomo and made it easy to integrate with developers’ code.
First of all, the dependency of Reladomo is simple. Only six JAR packages (the main library JAR file and five shallow dependent JAR files) need to be set up in the CLASSPATH at run time. For a full production deployment, you only need to present a driver class, slF4J logging implementation, and the developer’s own code. This gives developers a great deal of freedom to pull in other required modules as needed without worrying about JAR conflicts.
Second, we made sure backward compatibility was provided in Reladomo. Developers can upgrade the Reladomo version without breaking their own code. If there are changes in our plan that could cause backward incompatibility. We will ensure that developers have at least a year to convert to the new API.
conclusion
While we take usability very seriously (” Bring it all! ), but we also know that there are so many different use cases that it’s unrealistic to be all things to all people.
A problem that plagues traditional ORM is Leaky Abstraction. Once our core values are realized, we create an amazing system that avoids all abstraction loopholes. There is no native support for queries or stored procedures in Reladomo, which is by design. We try to avoid writing documents that read something like “If the underlying database supports feature B, it also supports feature A.”
Reladomo also has more features that have not been mentioned in this article. Please visit our Github codebase for documentation and Katas (our series of Reladomo tutorials). The second part of this article, due out in June, will show some of Reladomo’s performance, testability, and enterprise features.
Author’s brief introduction
Mohammad Rezaei is the lead architect of Reladomo and a Technology Fellow in the Platform business group at Goldman Sachs. He has extensive experience writing high-performance Java code in a variety of environments, from partitioned, concurrent trading systems, to large-memory systems with lock-free algorithms for maximum throughput. Mohammad has an undergraduate degree in computer science from the University of Pennsylvania and a PhD in physics from Cornell University.
Introducing Reladomo-Enterprise Open Source Java ORM, Batteries Included!