2008/07/08

Part three: Modeling the business logic

kick it on DotNetKicks.com

In the second part of the tutorial we have been modeling the class diagram of the web application that can be tested using the url http://www.blogger.com/post-edit.g?blogID=2481650812860323753&postID=6027216049265152531# . In this third part, Iwill show you how easy it is to define real business logic on top of the class diagram that we created.

Now we are going to implement the business logic in the model on top of the classes, attributes and relationships that we have just defined. The OlivaNova modeler enables us to define almost the entire logics in a formal language called OASIS. It looks a little bit similar to SQL but also allows us to make method calls and so on. Instead of explaining the theory behind that language, I will first show you an example.

In the screenshot below, I have defined a precondition for the method that creates an instance of the class TravelApplication. This method is used whenever an instance of TravelApplication needs to be created – even if the method is wrapped into another method. So, here is how we define a precondition with a simple OASIS-statement:



I think the statement defined in the formula is self-explanatory. If the method is executed, the system checks if the statement is true, and if it is not, it displays the error message to the user.
Now, we are going to add another precondition. This time, we want to make sure that the end time is greater than the start time if the start date is the same as the end date:



This editor window appears if you click on the magnifying glass right beneath the formula text field. The following screenshot shows two preconditions. Please note that we cannot yet check attributes in our preconditions but only parameters. This is due to the fact that the object does not exist when the precondition is executed (because the precondition is defined for a method that is just about to create the object).



If you enter a syntactically incorrect statement, and want to save that precondition using the “+” button, the modeler fails validating the statement and will tell you so.
In order to show you a slightly more complex sample of a precondition. If an administrator needs to delete a receipt type, he can use the transaction TDELETE in the class ReceiptType. If there isn’t any receipt that uses the receipt type that the administrator is about to delete, we are done. But what should happen, if there are already receipts associated with that receipt type? In this case, the transaction TDELETE enables the administrator to specify another receipt type that is going to be assigned instead of the receipt type that he is about to delete. So, we must be sure that either there are no receipts associated with the receipt type or that the user specifies a new receipt type to use. Of course, the receipt type must not be the same as the receipt type to be deleted.
Therefore, I added two preconditions to the transaction TDELETE in class ReceiptType:



The first one checks if the inbound argument specified does not equal to THIS (= to the receipt type the user is about to delete). The second precondition checks that there are either no associated receipts OR the user chose a receipt type as inbound argument that is different from NULL.

This way, you can define almost all constraints defined in the specification.

This leads us to the next big issue: methods. The concept of methods is very similar to what you probably know from your programming experience, but there are some important differences. Methods are called “Services” in the Olivanova System, and there are three kinds of “services”:

  • Events: … are not what you might think. In the context of OlivaNova, events are primitive data processing actions that run in a transactional context (in terms of database transactionality)
  • Transactions: … is logics defined in OASIS that can also call other events and transactions. They are executed as a whole or – if an error occurs – rolled back.
  • Operations: … are just like transactions but do not provide transactionality. This, on one hand can make your database logically inconsistent (if it fails during execution) but on the other hand no transaction log has to be made and thus, operations are somewhat faster in execution than transactions are (especially for massive calculations on data).

The following screenshot shows the dialog that allows the definition of those methods (input parameters, output parameters, preconditions …).



So far, everything besides the preconditions of the event create_instance has been automatically generated by the modeler. The Ins* and Del* events are events for establishing and deleting relationships at runtime between objects as defined in the class diagram.
The interface might irritate you a little bit because it is not so intuitive, but once you are used to work with the modeler it is quite okay.

So, back to the service editing form … on the left you see all methods (“services”) defined until now, and on the right side, you can see all input arguments defined for the selected service on the left side. Because I do not want most services to be directly accessible to the end user through the interface, I set many of the services to be for “Internal use”. This means, that those services can only be accessed through internal logics.
For now you can forget about the rest of all those buttons etc.

A few lines above, I explained to you how preconditions work. Now, I am going to use the sample above again (ReceiptType.TDELETE) in order to give you an idea of how transactions look like in OASIS.

The Transaction code of the transaction mentioned above looks like this:



I will explain those lines briefly:

{ia_newReceiptType <> NULL}
FOR ALL Receipt DO Receipt.TCHANGERECEIPTTYPE(Receipt, ia_newReceiptType).

delete_instance(THIS)

{ia_newReceiptType <> NULL} are so-called “guards”. Actually, they are nothing else than if-statements without the else-statement. So, the statement checks if the inbound argument ia_newReceiptType is not null and if it is not null, the line below is executed.

FOR ALL Receipt DO Receipt.TCHANGERECEIPTTYPE(Receipt, ia_newReceiptType).

This line is more interesting. FOR ALL belongs to the group of collection operators, that means, that they operate on a collection of objects and give you the ability to do something on those objects. In this case, the code iterates over all associated receipts with the receipt type the administrator selected to delete, and executes the transaction TCHANGERECEIPTTYPE in the class Receipt. The first argument of the transaction is the receipt object on which the transaction is executed and the second argument is the new receipt type to use when the current one has been deleted.

After this transaction has been executed (it just deletes the association from the receipe to the receipt-type to delete and creates an association to the new receipt type), the event

delete_instance(THIS)

is executed, which causes the THIS object to be deleted permanently.

To get an overview about what is going on in the whole transaction (which of course can span various classes), it is a good idea to click on the blue tree symbol on the right border of the dialog. The following window pops up and shows the whole execution workflow:



This way, almost any business functionality can be implemented.

Data visibility

Of course, we need to ensure that any employee or manager using the application is able to see and edit only the data he or she is allowed to see. Therefore, Olivanova provides several concepts, and you will know about them within a few moments.

First of all, we need to make sure, that a manager sees only the travel applications of the employees in his or her department. Because a manager is also an employee, he or she do also have a department assigned. If we try to formalize our security requirement, we could say that a travel application is visible to the current manager user when the employee that created the travel application is assigned to the same department as the current manager is.
In OASIS, this statement looks like this (assuming that we are starting at a TravelApplication object):

Employee.Department = AGENT.Department

AGENT means the currently logged in manager in this context. You may wonder how the system knows which role we are actually talking about, but if you take a look at the following screenshot, it becomes clear:



The “Server class” is the class whose services, attributes and roles (=associations) are displayed on the left side and can be moved to the right side in order to make them accessible to an interactive user of class Manager (bottom). On the left bottom of the screenshot you see a section “Formula” which contains the formula we just defined. Because the formula is valid only for managers that try to access TravelApplication objects, the keyword AGENT is unambiguous. Furthermore, there are other concepts in the modeler that allow you to specify the evaluation of expressions for a certain role only, so – for example – you could specify that a certain precondition only applies to employees but not to managers.

Next, we need to specify which TravelApplications an employee can see and what he or she can do with TravelApplication. We want the employee to create and edit his or her own travel applications, see all fields but do not allow him or her to approve or reject a travel application.



As you can see, each agent of type Employee is allowed to execute all services except for reject and TAPPROVE, and he or she is allowed to see all attributes and associated of the TravelApplication provided the fact that the Employee of the TravelApplication is the currently logged in user.

So, after doing some modeling cosmetiques, I want to know if I have any serious bugs in my model. Therefore, I click on the Checkbox-symbol on the modeler toolbar and the following window pops up to show me the model validation output. The model validation checks the whole model for errors and only if there are not any errors in the model, I am able to send the model file to the transformation service.



There is one warning in the above picture we do not have to care about right now. Since there are no errors, we could get a working application right now. But we are not yet satisfied because there are some more improvements we can do for our customer.

Check out the next part to see how we are going to model the user interface.

kick it on DotNetKicks.com

Keine Kommentare: