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.
Subscribe to:
Post Comments (Atom)
2 comments:
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?
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