Thursday, September 8, 2011

Enterprise Java Beans - A Broad Overview

What are Enterprise Java Beans: Enterprise java beans are Java EE server side business layer components. They can be used to develop highly available distributed transactional systems. Like servlets and JSPs are used to develop view components in java EE, EJBs are used to develop business logic. Today, I am not going to discuss how an EJB can be deployed on the server. There are just too many tutorials available on the internet. I will discuss the need to use EJB, where it fits and what it gives. In the recent times, people have preferred Spring framework instead of EJB, the advantage of what is being lightweight. It however needs to be discussed that being lightweight is not the only qualities a business framework needs to have. There is a limit to how much spring can scale. There are also somethings spring simply cannot handle, not at least without external component injected through its AOP framework. Enterprise Java Beans are complex and heavier weight, because they attempt to provide solution to much more complex regime of problems. Also note that, unlike spring, EJB is not a product, its a specification. Which means you would have a choice among many implementations, both proprietary and open source, of EJB; where as there is only one spring.

However, after EJB 3.x, deploying and programming EJBs are easier than doing so with spring... believe me.

Remote Procedure Calls: One of the fundamental differences between EJB and spring is that EJB supports invocation remotely, meaning the method of an EJB can be invoked over a network. This is a fundamental requirement for scaling up. EJB has two choices to implement this - Remote Method Invocation (RMI) and Common Object Request Broker Architecture (CORBA). RMI is optional for an EJB container to implement, whereas CORBA is mandatory.

The difference between RMI and CORBA is that RMI is java only, but CORBA is language independent. Since RMI is optional, I will discuss CORBA only.

CORBA: CORBA is a specification that is managed by Object Management Group (OMG) that attempts to solve the problem of integration between solutions implemented using different technologies. While doing so, it also attempts to solve the problem of remote method invocation. To achieve this interoperability, it must define some mechanism to specify the message format exchanged between different systems. For this, CORBA defines a language called Interface Definition Language (IDL). IDL can only define interfaces and not implementations. It defines what a remote object must implement.

Any object system that supports CORBA will have mechanism to create either dynamic invocation interfaces or stubs. Stubs are created by compiling IDL into native language, where as dynamic invocation interface is a pre-built component. Now what are these components? Both stubs and dynamic interfaces (not to be confused with java interfaces) are components (like a class or a function) that have the necessary implementation to connect to the ORB systems, translate the native object format into ORB compliant format, and then send or receive messages, so as to interact with the remote object.

The implementation of remote object can also be in multiple languages. The server side equivalent of a stub or a dynamic interface is a skeleton. skeletons can thus be either dynamic or static IDL skeleton (i.e. compiled from IDL). The ORB framework's job is to carry the message from client stub to server skeletons and vice versa. The following diagram depicts this.

Now lets come back to EJB. In EJB, the implementation must be written in java. Also, writing IDL is difficult for a java programmer. So, EJB simply lets the developer to specify the interface in java, and then generates IDL from that.

Note that the CORBA does not specify how the remote object is instantiated. The particular ORB implementation is free to instantiate the object based on the client requests and the nature of the object.

Transactional Systems: A transaction is a unit of changes in the system's durable state (database) that must be atomic or undividable. It means either all of the changes must occur, or none of them must occur. A transaction must have these properties - atomic, consistent, isolated, durable (ACID). Atomicity I have already explained. The system must be in consistent state after the transaction created changes or failed to do so. Being isolated means in a multi-threaded, multi-process system, the transactions should occur in a way that they appear to be occurring serially; i.e. it appears for any transaction T1, that any other transaction T2 either happened earlier or later, but not both. Lastly, if the changes are not durable, there is no point of getting into all this stuff anyway - hence a transaction must be durable.

Now how does a system guarantee those properties? I will discuss one by one.

Atomicity: Atomicity is provided by a two step process. First a list of changes to be made must be created, which would be called the change log. At this stage, it is verified that the changes can be made (primary key constraints, foreign key constraint, other constraints etc.). After which the change is actually committed. Now if something wrong happens before the change log is completely created (for example the power fails), the whole change is completely ignored when the system comes up again and the change log is discarded, leaving the system unchanged and thus in a consistent state. If something goes wrong while committing the change, well the change log know what needed to change and the rest of the change is committed when the system comes up again. This way, the consistency is pretty guaranteed for single threaded single process systems. It however can cause some problems in multi-threaded multi-process systems as we will see.

Isolation: Isolation is so much more complex than atomicity, it is in fact so complex that it is not even implemented ... not at least to the full extent. The SQL specification specifies four levels of isolation - Read Uncommitted (no isolation), Read Committed, Repeatable Read and Serializable (full isolation). In read uncommitted level, the uncommitted changes made by one transaction is visible to other transactions. Thus there is no isolation.

In read committed level, only committed changes are visible to other transactions. Now, let us consider the famous ATM card example - Two people have a joint account and thus have their individual cards A and B. Now, ATM operation is a transaction involving broadly two operations - deliver cash at ATM and debit the account. Now lets assume the account originally has $5000, both start separate transactions of $2000, transaction A reads the amount $5000, debits $2000 to make it $3000. Meanwhile, transaction B reads the balance. Now since A is not committed, B still reads $5000, and then debits $2000. Now A delivers cash and commits the value $3000. B delivers cash and commits the value $3000. In effect, one debit is lost and now the system state is inconsistent (the records show that the account has $3000, but the bank does not have that much money).

The above is a problem of non-repeatable read. After transaction A is committed, if B reads the same value again, it gets a different result from the value it originally got (thus it can now see that some other transaction happened while it was executing).

This problem can be solved by the repeatable read isolation level. In this level, once a transaction selects a record, that record is not writable by any other transaction. If we had used the isolation level in the above scenario, A cannot write to the record, since B has read the same row, also B cannot write since A has read it. Its a dead lock situation. A database will kill any of them and after which the system will be in a consistent state.

However, in this level, if a range has been selected, a new row can appear when another transaction inserts a new row, since only the rows that are selected gets locked. Suppose there is another transaction C happening which only checks the balance. Now let us assume that in a day, only 100 transactions (including views) are allowed. Let us assume that each transaction is recorded in a log, and the number of entries selected for a day must not exceed 100. Now, let transaction A start by selecting all transactions in that day and 99 records are fetched. Hence A proceeds. Meanwhile, C also selects all the transactions in the day and get 99 of them (since A has not committed any changes, and C is allowed to read anything that is committed). At the end both insert a new row (each, since inserting new row is allowed as the new row was never selected). At the end of both transactions, there are 101 transactions at the end of the day. Hence the system state is inconsistent.

In the above scenario, if A executes the same query after C is finished committing, it will see a new row. This is called phantom read.

This can be solved by serializable isolation level which applies range locks in case range selection is done. This will seriously reduce the system performance and hence is avoided. Note that in the above scenario, we can simply store the number of transactions for an account in a separate table in a single row, in which case a repeatable read will suffice (either A or C will fail as they now have to update the same row, thus maintaining the consistent system state).

However, EJB only officially supports an isolation level of read committed. It recommends developers to use optimistic locking to achieve the effect of repeatable read.

Optimistic Locking: This is not technically locking, but achieves the same effect in a different way. All tables are given a version column. The version must be incremented every time the row is updated. Say in the above scenario, A selects the account balance and gets version 5. B also selects that row and gets the same version number (since A has not committed). A updates with query update account set balance=3000, version=version+1 where account=1232131 and version=5 and commits. Now B executes the same query (because it read the same version number during selection) and since after A committed, the version number is 6, B does not update anything. B knows that the update did not happen (JDBC returns the number of rows that are updated) and thus rolls back (or fails). The system state is thus consistent. EJB 3.x uses JPA for persistence which has built in support for optimistic locking.

Distributed Transactions: A transaction is distributed if it involves multiple systems. For example, if the transaction involves multiple database systems, or in our example, the ATMs and the database are different systems. Distributed transactional systems are implemented using transactional nodes and two phase commits. (This is something you are not likely to get in spring).

Each transactional node must maintain an undo log and a redo log of changes. A transaction manager maintains a list of all nodes. When a commit is requested, the transaction manager sends a list of changes to each node (as appropriate to each node). Each node then checks whether the change can be committed and based on that returns a reply commit/roll-back. If commit is the reply, it also creates an undo log and a redo log for changes. If all the nodes have replied commit, the transaction manager sends a message to each node for commit. Then they can commit according to the redo log. If any of the node replies roll-back, the transaction manager send roll-back message to each node, which then roll back according to the undo-log.

Hierarchical Transactions: In this case, a transaction manager can have both a node (for example a database or another transaction manager as its child. A child transaction manager can then have nodes and transaction managers as its children, thus creating a tree structure. This way, a transaction can span over a multitude of systems.

Due to this two phase commit process and hierarchical transactional model, EJB becomes heavy weight. But its potential covers very complex systems, involving multitude of systems with different architectures, all integrated into one system, that can perform ACID transactions. This is really impressive. A lot of technology is involved, which I will cover in the coming articles. Stay in touch.


Anonymous said...

Another answer to interoperability of heterogeneous distributed systems is to use web services and http as the glue. How do you see EJBs as a better or more appropriate solution as compared to web services? It seems as though they both rely on a "serialization" mechanism. CORBA, stubs and skeletons are replaced by wsdl and (in a JAVA context), jaxb. If the business interaction can be satisfied by a simple request/response model, without distributed database transactions, or complex interactions, isn't a web sevices model adequate?

Debasish Ray Chawdhuri said...

There is no standard way to make web service based integration transactional. Normally if you only need to integrate within the same organization through intranet, EJB would be better not just because of transaction, its also faster and requires less traffic. The only reason for using web-service is to integrate heterogeneous systems from multiple solutions providers or if the communication is needed between to different parties that would require the use of the internet. Since CORBA traffic would hardly get through the existing firewalls and HTTP will always get through, communication through internet would be more preferably through web services.

That's not to say we cannot have transactional interactions through web-services, its just that there is no standard way of doing it, you probably would end up writing your own model.

Post a Comment