I've described a domain model (see attachment) and a suggestion how to implement this using iMo. It's very likely we are not the first having this problem but for some reason it's hard to find the right patterns. Would you please be so kind to share some thoughts.
The Contract objects owns 1 or more Configurations, each Configuration owns it's own FileSpec. (see attachment) Each object is implemented as a business entity to enable us to re-use these in a different context. Both the Contract and Configuration object have a kind of versioning. Each time a user changes the Contract, a new version is created.
The user needs to be able to define contracts. To add a new contract he first needs to create a Contract, create one or more new Configurations and create a FileSpec for each Configuration of the Contract. When the user presses 'Save' everything needs to be saved in a single transaction. We are planning to make a wizard style user-interface for this.
The only way to get a transaction is by saving all information in a single AppServer call. Model classes are designed to operate on a single dataset attached to a single business entity. To get this work we need to store all information in a single dataset and forward it to a business task method. This method needs to receive the dataset and use it to create and store a new Contract and the other business entities. We will also need to have a method for updating a Contract. By doing this we can support our user-interface and expose it as a service to others.
In previous projects we worked around this kind of scenario's. The user needed to create and save Configurations first. After that he creates a Contract, adds the Configurations to the Contract and saves the contract. These were 2 separate MVC's and transactions. In the user-interface we maintained the business entities. For simple business entities this is fine but it doesn't suit for complex business objects.
Business entities can have properties you don't want to be changed by users or other external components. For instance the properties reflecting the state of an object. Business entities are not able to block this. If a business entity was a real object it's quite easy to block the write of properties but datasets don't have this kind of functionality. We discussed this earlier, a producer and a consumer interface for saving datasets might resolve this.
The user-interface only sends the changes you made to the original dataset. If I fetch an order dataset, remove a orderline and send it back to the server, the business entity cannot check the number of orderlines of the order. To do this it needs to fetch the order from the database, apply the changes and validate the business entity in the dataset. It needs to observe the object in the dataset, not parts of it.
Separating user-interface and business entity datasets resolves some problems but adds some extra complexibility to the system. Important is you can change the internal structure of your business entity without having to change the interface of your application.
Am I on the right track?
For those using other user-interfaces: How do you implement user-interfaces for this? Are you using multiple models?
Now I'm having a problem in mapping the validation errors (exceptions) of my business entities to the dataset I received from the user-interface.
When the business entities validate their dataset, validation errors are added as exceptions. These validation errors need to be 'moved' to the dataset of the user-interface.
The user-interface uses the exceptions to mark input-fields having errors.
The rows of the dataset having an error-status save the id of the attached exception in the error-string (ContextBuffer:error-string = bException.exceptionId).
I think I have to resolve my problem by finding the rows with an error, take the exceptionid from the error-string and assign it to the corresponding buffer in my user-interface dataset.
Because the context of the error is the context of the business entity I have to change the field-name in the exceptionContext of the exeption too!
How can I do this, we don't have access to the exception dataset.
| Attachment | Size |
|---|---|
| screenshot.JPG | 35.82 KB |
oera is fractal?
After reading your question a couple of times (up to the "Am I on the right track" part) I come to think that OERA should be fractal, but isn't yet. Let me try to explain what I mean...
A simplified view on OERA: the appserver currently has roughly two layers, the business logic layer and the data access layer.
The business logic layer applies business-rules to objects (known as business entities) that are modelled the way a business analyst person would design them.
The data access layer applies technical rules and specs, in order to map data in the business entity object to the low-level storage engine(s).
The assumption is that because business entities are modelled from a business viewpoint, they will fit well on the user interface.
Your example shows that the user interface demands a business object that is not quite the same as a business entity. It is perhaps a "composite business entity" that combines and contains three lower-level business entities (contract + configuration + filespec). The composite business entity has some extra business-rules (like some data-members are read-only, or even hidden) and maybe the object structure is even different that just a dataset that contains three datasets. Maybe it is not a composite business entity, but a transformed object that better fits the needs of the consumer than the original object. Either way, it is an object between the current Business Entity and its consumer.
By lack of other tools you are tempted to do all this in a Business Task object: use the BT to fetch a bunch of BE's and combine them into a new Entity. But that would not quite right. A Business Task is not meant for the definition of an Entity, but for the implementation of verbs like for example "PAY the invoice", "SEND the ordered goods", "PURCHASE out-of-stock items". Verbs you would use as steps in a workflow. Even if a BT could be made to do all that you want, it would not be great design I guess.
I think it would be better if there was a class that implements the BE interface (i.e. it has methods fetchdata and storedata and more) but instead of being associated with a DataAccess class directly, it would call fetchdata in 1 or more lower-level BE classes and combine their data into a new higher-level dataset.
An other way to say it: just like how a DataAccess class joins, maps and transforms data from multiple low-level tables into a Business Entity, you could have a class that joins, maps and transform data from multiple Business Entities into a broader Business Entity.
So, BE can be regarded fractal because a BE can contain multiple BE's, in which case there would be a sort of DA++ that knows how to map the lower-level BE-data to a higher-level BE model. This DA++ would not access the database at all, it would access lower-level BE's instead who in turn access DA's that access the database.
Or is it not true anymore, that DA's access the database?
Recently I heard a discussion on wishlist item "support multiple databases" like OpenEdge, Oracle, MySL, MS-SQL, and perhaps more brands. The discussion was whether the DA class implementations (like daOrder, daCustomer) should be allowed to contain statements like FIND FIRST and FOR EACH because those kind of statements may not work well with different databases. Brainstorm ideas were to have a buffer class with Find and Foreach methods and use that from within the DA implementations, or move more of the joining/mapping/transformation from the DA classes to the BE classes (typically for adding description lookups) where the BE classes would do their work by fetching datasets from different DA's. My personal opinion is that adding descriptions (just like other related data) is responsibility of the DA layer, not the BE layer, so if the DA class is not the place to do it then I would rather design a layer in between the BE and DE. And if the DA classes are going to delegate the accessing of data to a buffer class with Find and Foreach methods, then the DA class itself has become a "layer in between".
So, a DA is also fractal! I mean in the sense that the current idea of DA classes do not hit the database anymore, but fetch data from BE's or sideways from other DA's or downwards from low-level data-access classes that implement database-vendor specific drivers.
Now if I am right and there is indeed a case for "fractalism", then I think we can design and make the base classes and methods that propagate Exception records and so on.
For the business layer we
For the business layer we need a kind of 'application layer' running on the server side. We now 'misuse' the BT for it. On the client side it implements the iBusinessEntity interface (fetch and store), exactly as needed. But it would be good to have another class for it which name better covers its load. In BDK we had an Application Logic Object for it.
Connecting Business Entities, etc
In my opinion iMo should offer a facility to define/relate/bind BE's based on (for example) Use Cases.
In 1 use case we only need personal info, in another case i need person,customer,order info.
Usecases could even be combined together.
Instantiating the complete functional entitymodel (read fetch all the data) for all different usecases all the time is
not convenient.
Define a usecase. Define a tree with related BE's for this case.
From the definition we can instantiate the object structure / fetch the data.
Next step would be extracting the functional classes/beans from the code to meta definitions
Another facility regarding stateless beans: Behind the BE(an)Factory we could define a object pool/container/object manager
containing objects ready to use. Things won't be that 'expensive' anymore.