The Why, What, and Where of Custom AMF Class Adapters
If you’ve read my article on Action Message Format (AMF) you’ll already know that AMF is a super-duper way to transfer data between a Flex/Flash/AIR application and its server-side counterpart. Essentially when the client application makes a call to a service, the server can return a Java (or PHP, Python, .NET) object and when it gets back to the Flex application, wallah, its converted into an identical object in ActionScript. Similarly, if the client application sends an ActionScript object to the server during a service call, it arrives as an identical Java object.
While that’s impressive, when it comes to implementation in a medium-to-high complexity system there are questions that still need to be answered. In this article, I’d like to address where to translate custom AMF classes.
When server-side code accesses data from a database, it generally converts the data in the recordset into data objects (sometimes called beans, business objects, or one of many other names.) These are basic objects that often, though not always, represent the shema of the database. In other words, if there’s a table named customer in the database, there will likely be a class named customer as well. One customer object instance represents one customer record in the database. With frameworks like Rails, Cake, and Django, this is almost always true.
If a Flex application is requesting all customers, the process of retrieving the data is fairly simple: Flex makes a call to the service, the service retrieves all customer records from the database, the service converts each customer record into an customer object, and finally the service returns the array of customer objects back to the Flex application.
In order for the previous example to function correctly, an ActionScript customer class must exist on the Flex client as well as an identical Java customer class on the server. Considering the most common case, these two classes will have properties that match the fields in the customer table. In other words, both of these classes as well as the customer table will have properties such as firstName, lastName, phoneNumber, address, zipcode, etc. With that in mind, what happens when a developer begins to refactor the application and chooses to rename the phoneNumber database field to homePhone? The phoneNumber property in both the ActionScript customer class and the Java customer class will need to be renamed to homePhone. This means there is high coupling among the database, the backend application, and the frontend application. In other words, each part needs to know about the other two parts. This breaks good programming principles because it results in more difficult maintenance.
This high coupling magnifies itself further if we wish to have the Flex application’s needs drive the design of the data objects. Lets say we as Flex developers are creating a Flex application and know we want a salesperson’s first name, last name, sales code, and whether the salesperson is a system admin. Now let’s say the database has already been designed and for whatever reason the salesperson’s firstName and lastName are in one table, the sales code is in a different table, and the system admin rights are in another. Without some sort of conversion adapter, we will be shipping objects created from three different classes (Class A related to Table A, Class B related to Table B, and Class C related to Table C) from the server to the Flex frontend. The following diagram illustrates the data flow.
Why does this high coupling present a problem? Here are a few issues to think about:
(1) If the backend developer chooses to refactor the database table by combining Table A and Table B, the Java classes AND the ActionScript classes must also be refactored. If we’re talking about production code, that means refactoring ActionScript code and redeploying the SWF. In the case of an AIR app, the AIR application on each client computer must be upgraded.
(2) The Flex frontend probably does not, and to promote low coupling, should not, care about how the database was designed. This is more an issue of principle, but if the frontend developer is mentally detached from the database schema, the frontend application’s modeling, logic, and architecture will in general likely also be decoupled from the database schema.
(3) Having three different ActionScript classes to get our salesperson information doesn’t necessarily make sense. Maybe there was a reason to have the information in three different database tables, but the reasoning may not apply when it comes to the Flex frontend. Having three different classes means more overhead and maintenance.
(4) Even simple differences such as coding style must be maintained across the database, backend code, and frontend code. If the database fields were creating using underscores such as first_name, last_name, home_phone, etc., this style must be also be used in the backend code and the frontend code. Considering most Flex developers use camelCase name styling (firstName, lastName, homePhone,) this coupling presents an inconsistency in style.
So, how do we deal with these issues? Generally the best solution is to create an adapter or layer that converts objects the backend uses to objects the frontend needs. The backend and frontend agree on the classes that will be sent across the wire and those classes essentially become a contract — an agreement between the frontend and backend on what will be sent and received. These classes should be designed without regard to database schema or the classes the backend uses within its own domain. In our example, Flex needs objects of Class X which includes a salesperson’s first name, last name, sales code, and whether the salesperson is a system admin. The backend is already using Class A, Class B, and Class C that contain these various properties. The adapter would take objects of Class A, Class B, and Class C and combine the needed properties into an object of Class X. Class X is essentially the contract — both the backend and frontend have agreed that Class X is what will be sent and received.
Not only does an adapter allow for the creation of objects that are slim-and-trim and customized for Flex’s needs, but of even greater importance is it provides a point of decoupling. If the field in a database table gets renamed, the related property in the frontend class does not need to be renamed. Instead, only the adapter needs modified so the new backend property name is mapped to the old frontend property name. This adapter also provides for a place to convert programming style (first_name on the backend would be mapped to firstName on the frontend.)
Now that you (hopefully) understand what adapters are and why they’re helpful, the remaining question is where the adapter should be implemented. Two possible locations are the most logical: (1) on the backend immediately before sending objects to the frontend or (2) on the frontend immediately after receiving objects from the backend. The data flow process for each of these adapter locations is illustrated in the following diagrams.
Here are the benefits of each location as I see them:
On the backend immediately before sending objects to the frontend:
- Database schema changes do not have any affect on frontend code (except for changes fundamental to application logic.) This means when the database schema changes, the Flex/Flash/AIR application does not need to be recompiled and redeployed. If the adapter were on the frontend, the adapter would need to be updated, resulting in the need to be recompiled and redeployed.
- In a team environment, a backend developer can modify the database schema and commit changes to the backend classes and accompanying adapter almost simultaneously. Frontend development remains virtually uninterrupted. If the adapter was on the frontend, the backend developer would make the change to the database schema and commit the changes to the backend classes. The backend developer would then need to notify the frontend developer of the change. The frontend developer would then modify the adapter and commit its change. Throughout the duration between the backend developer’s commit and the frontend developer’s commit, the Flex application would not function correctly. Because the frontend is generally more concerned with the presentation of data rather than heavy analysis, data mining, integration with ecommerce, and other data-centric tasks, frontend developers will likely require less refactoring of data models than backend developers. By having the adapter on the backend, less communication between developers will be required when refactoring.
- Keeping adapter code out of the frontend results in a smaller SWF/AIR file size.
- Conversion tasks carried out by the adapter require processing. By keeping processing on the server, processing time is more predictable and manageable than if the processing were performed on the client.
On the frontend immediately after receiving objects from the backend:
- The backend does not need to know how frontend data is modeled. In other words, if a frontend developer would like to rename a property in the data model’s class or combine two classes, the backend does not need to be notified. If the adapter were instead on the backend, the application would be broken during the duration between when the frontend developer commits the data model’s class change and the backend developer commits the backend class and adapter change. However, like I mentioned previously, it’s been my experience that the frontend requires less data model refactoring than the backend.
As you may have concluded from the analysis, it’s fairly clear the best location for the adapter is on the backend just before sending objects to the frontend. While having an adapter or at least some level of abstraction in the first place is certainly the most important concept, having an adapter in the right location can also help.
Do you disagree or have a perspective not addressed in my analysis? Do you have questions regarding the why, what, and where of custom class adapters? I invite you to join in on the conversation by responding below.