Why you shouldn’t use class mapping and VOs in amfphp

Posted on Thursday 18 January 2007

I am not one to start a fight over the use of this or that pattern, but since Flex 2 has arrived, a lot of people have started using VOs, TOs and DTOs (which are more or less the same, as I understand it) in amfphp, a trend I have some issues with.

What other people have to say about VOs

The motivation behind TransferObjects, according to the Sun design pattern site, is:

Some entities contain a group of attributes that are always accessed together. Accessing these attributes in a fine-grained manner through a remote interface causes network traffic and high latency, and consumes server resources unnecessarily.

A transfer object is a serializable class that groups related attributes, forming a composite value. This class is used as the return type of a remote business method. Clients receive instances of this class by calling coarse-grained business methods, and then locally access the fine-grained values within the transfer object. Fetching multiple values in one server roundtrip decreases network traffic and minimizes latency and server resource usage.

According to the ARP manual:

We use Value Objects [...] to model problem domain objects and structure the exchange of data among the various tiers.

According to the c2 wiki:

The reason you want to use an actual type [for a DTO] rather than a Hashtable is that the lookup of instance variables is orders of magnitude faster than looking something up in a hashtable. It also has nice features from a type safety perspective.

And again:

The DTO is typically implemented as a class with a bunch of fields, and getters and setters for each field.

Since every DTO class is essentially the same, except the field names and data types, why have I never seen this refactored? Isn't a generic DTO a simpler solution? If the data constraints were defined in a generic way then a simple user interface could be built automatically: create, search, modify, delete.

With the answer:

Yup, that's a recordset, and it kills the advantage of static typing and you end up always trying to figure out what's in it. Better to use real objects with real properties. aPerson.Name is better than aPerson["Name"], because it's easier to maintain in the face of change, the compiler will help you.

And a counter answer:

I'm not advocating throwing out typing altogether. I've known people who have been on projects where the DTO was a HashMap. They said it was far more hassle than it was worth precisely because of the lack of type checking. I would like to use a data transfer object that was constrained to a particular schema, like a class definition, but checked at runtime.

How this relates to amfphp

So basically a VO or DTO is a "throwaway" object which is to used for the transfer of data across a wire. The main advantages are: they define a serializable interface, and they are type-checked. Now the first argument doesn't make any sense in the context of Remoting, where everything has a serializable interface (of sorts). The second argument has a lot more weight, that is, type checking.

Let me state the obvious here: PHP is a dynamic language. That means that if you send a VO from Flex to amfphp, you will have to check yourself whether you are receiving what you are expecting. Consider the following in Java:


void addUser(UserVO userVO)
 

and in PHP:


function addUser($userVO)
 

You can immediately see that while in Java you can rest assured that the first argument you receive is a UserVO, that is not the case in PHP. There is nothing that tells you that instead of getting a UserVO you get an AdminVO, a stdClass object, or even a boolean. So you're going to have to add code to check whether you're indeed receiving what you're expecting. Also, if what you want is your objects to be sealed, so that other values can't be added to the VO, again, that won't work, because PHP objects are dynamic. You could use E_STRICT mode, but amfphp won't run in E_STRICT mode, as it supports PHP4 (SabreAMF does support E_STRICT mode).

Why sending non-dumb objects is also a bad idea

So you can see that from the preceding discussion that you don't get any clear advantage from sending a VO to amfphp as opposed to a plain object (associative array), given that the object is "dumb". What if the object was not dumb (ie, it has methods defined on it)? Then you could do the following:


function addUser($userVO) {
    return $userVO->save();
}
 

Admit that the save function saves the VO to a table in a database. You say, how convenient. However, the issue here is again that you have no idea whether you are receiving what you are expecting. So if instead a user would send an AdminVO to the user function, he could add an admin to the database if the object defined the same save function. Bad. So you have to check the type before saving. What if there are side-effects in the constructor? That means that the code in the constructor of AdminVO will be executed, even before you check that you are expecting a UserVO. If the AdminVO connected to a database with higher rights in the constructor, for example, well, you see where this is going.

Basically using class mapping can make you open to using remote code execution. Now the whole point of amfphp is remote code execution, but the issue here is that the kind of side-effects and issues with it can be very subtle, so it's much easier to miss. That is why I've purposefully hidden the class mapping features so that only an experienced developer who would be aware of these issues would bother to look into them.

Other reasons why you're better off sending a plain object

If you use (incoming) class mapping, your code will break if you attempt to use other RPC protocols which don't support that feature (XML-RPC, JSON). In addition, associative arrays have more methods available to them which can effectively lower the amount of code that you write. For example, I saw the following code recently:


function Register ($artistVO) {
$username = mysql_real_escape_string($artistVO->username);
$firstname = mysql_real_escape_string($artistVO->firstname);
$lastname = mysql_real_escape_string($artistVO->lastname);
$password = mysql_real_escape_string($artistVO->password);
$email = mysql_real_escape_string($artistVO->email);
$city = mysql_real_escape_string($artistVO->city);
$provstate = mysql_real_escape_string($artistVO->provstate);
$postzip = mysql_real_escape_string($artistVO->postzip);
$country = mysql_real_escape_string($artistVO->country);
 

If $artistVO was an associative array, then:


function Register ($artistVO) {
$escapedVO = array_map('mysql_real_escape_string', $artistVO);
 

Much cleaner.

Bottom line: don't use incoming class mapping, use outgoing class mapping

Now the typing arguments and the remote code execution issues become invalid when we consider the inverse process, sending class instances from amfphp to Flash/Flex, because Flash/Flex runs in a sandbox and AS2 is compile-time type-checked/AS3 is runtime type-checked. So you can certainly use $_explicitType to send back a typed object to Flash if you so wish. If your data looks like a RecordSet, however, don't bother looping through it to transform it to an array of VOs. Waste of time, especially since in AS2 RecordSet is much more powerful than Array.

As for incoming class mapping, remember that if amfphp doesn't find the class to be mapped in the services/vo folder, it will simply deserialize the object as though it were untyped, that is, as an associative array. So you don't have to change your UI code or anything, you can send typed objects to amfphp if you wish, you will receive it as an associative array fine unless you specifically put a class to the vo folder to enable class mapping.

Also, I don't want to start a storm or anything about best-practices and patterns. The VO pattern is fine, it just makes a lot more sense in Java than it does in PHP. There is no use in trying to make PHP code look like Java; Java already looks like Java, and if you like that look, then please, by all means, use OpenAMF or FDS.


WordPress database error: [Can't open file: 'wp_comments.MYD'. (errno: 145)]
SELECT * FROM wp_comments WHERE comment_post_ID = '235' AND comment_approved = '1' ORDER BY comment_date

No comments have been added to this post yet.

Leave a comment




Your e-mail address is never displayed. If you run into issues with SpamKarma blocking you, email me at $patrick->5etdemi(com)


RSS feed for comments on this post | TrackBack URI