Saturday, April 27, 2013

Restful URLs For Managing Entity Relationships

Web applications created by the scaffolding of Spring Roo's Web MVC add on map URLs using restful design principles.  Eventually as a developer you will need to add functionality that involve new request mappings.  So how should one create new mappings while maintaining consistency with both restful design principles and that provided by the scaffolding?

Controllers are generated by Spring Roo's scaffolding on a per entity basis.  For each entity a controller is generated for the following seven operations and URL request mappings are defined;

Operation Request Type URL
list GET /<app-name>/<entity>
show GET /<app-name>/<entity>/{id}
create form GET /<app-name>/<entity>?form
create POST /<app-name>/<entity>
update form GET /<app-name>/<entity>/{id}?form
update PUT /<app-name>/<entity>
delete DELETE /<app-name>/<entity>/{id}

This set of request mappings provide basic CRUD operations (show, create, update, and delete) along with ability to list entities, and display create and update forms.  For an independent entity this is all the operations that are typically needed.

In real life entities rarely exist independently of each other.  To have a consistent restful design for our applications, we need to develop a taxonomy to describe various roles that entities can assume in their interactions with each other.  We then need to develop a scheme to map those relationships into request mappings.  Typically when talking about entity relationships most the focus of the discussion is usually on the cardinality (1:1, 1:m, m:m) between entities, but here we are really more concerned about the nature of those relationships.

In considering the relationship between entities we must recognize entities can be either independent or dependent.  Independent entities do not dependent on other entities for their identity.  Such entities can exist by themselves, and may or may not be aware of dependent entities around them.  Dependent entities on the other hand rely on other or parent entities to establish their identity.  Dependent entities may also be existence dependent, meaning the entities existence is ;inked to its parents existence.  The role a dependent entity plays in regards to its parent can be characterized further into the following types;
  • Subtype -- entities of this type embellish another entity.  We don't have any of this type in the muti-tenant security model.
  • Characteristic -- entities of this type represent a group of attributes that can occur multiple times for another entity.
  • Associative -- entities of this type record relationships between other entities.
Consider the following entity relationship model for a multi-tenant security application;

In this model we do have independent entities like AppUser, Permission, and Organization.  But a majority of the entities in this model define relationships between the independent entities and are dependent.  Of the dependent entities here there are none that establish a subtype relationship.  But the AppRole entity is a characteristic of Organization.  The remaining entities OrganizationUser, OrganizationUserRole, and RolePermission are associative.

Since Roo provides the means to handle independent entities we need not be concerned about them.  And while we could manipulate the dependent and reference entities using Roo's default generated code, typically uses require a less technical and more intuitive interface.

To provide an intuitive interface we are going to have to extend our web application to allow users to manage dependent entities in a implicit manner.  For example we need to provide the ability for users to add and remove AppUsers from Organizations, but without exposing the concept of OrganizationUser.  But how do we want to define the request mappings such that they are restful, complement those that already exist in the application and hide the mechanics of managing relationships?

So lets look at the types of entity relationships and the type of functionality that we would expose to the user.

The functionality for subtypes is fairly easy to describe since both the subtype entity and its parent are locked together,  So when a operation is performed on a subtype we need to do the same to its parent.  For example when updating a subtype we should allow the user to update fields of the parent entity also.  And when that update is submitted both the subtype and its parent should be updated.  We can provide that linkage in code, but it shouldn't affect the subtypes restful URLs.

Functionality of characteristic relationships should be restricted to the scope of the entities parent.  So when listing such entities that list would be for a particular parent.  And when creating an entity the parent should already be known.

With associative entities the typical operations will be focused on adding and removing associations between independent entities. To create associations we will need to provide a form allowing multiple selection of candidate entities for inclusion into a relationship with another entity, along with a response service to handle the form submission.  For removing associations we will need to provide another form to allow multiple selection of current related entities, along with a subsequent service to handle the submission of that form.

Now that we have categorized the nature of the relationships that we could have in a entity model.  And we also have a good idea of type of functionality we want to expose to manage those relationships.  The next step now is to determine how we want to map that into functional units that will ultimately become request mappings.  To do this we will use the functionality associated with the request mappings that the Roo scaffolding already uses as the framework.

Functionality Subtype Characteristic Associative
list Roo default mapping applies. Should be restricted to those belonging to a parent. Not applicable.
show Default mapping applies, but need to include parent entity attributes. Default mapping and functionality applies. Show a list of the other associated entities. 
create form Default mapping applies, but need to include form elements of parent entity. Need to hide parent entity id. Show a multi-select list form with candidate entities for association.
create Default mapping applies, but need to create parent entity along with subtype entity. Default functionality and mapping applies. Provide ability to create multiple association entities.
update form Default mapping applies, but need to include form elements of parent entity. Default functionality and mapping applies. Not applicable.
update Default mapping applies, but need to update parent entity along with subtype entity. Default functionality and mapping applies. Not applicable.
delete form Not applicable. Not applicable. Show a multi-select list form with associated entities.
delete Default mapping applies, but need to delete parent entity along with subtype entity. Default mapping and functionality applies. Provide ability to delete multiple associating entities. 

As an astute reader you probably noticed the functionality we needed to map didn't align perfectly with what the scaffolding provides.  Principally for associative entities, we want to provide a multiple selection form to allow for the bulk deletion of associations.  To handle this we added delete form to our list of functionality.  Since the scaffolding treats all entities as independent entities and the concept of a delete form is not applicable to independent entities it doesn't appear in the controllers generated   But we need to add the operation to handle removing associative relationships.  Additionally adding it completes the symmetry that data modification functions (create, update, and delete) have corresponding form functions.

Finally we have arrived at the stage where we can determine a set of URL patterns that we can apply consistently throughout a web application.  With subtype entities we don't have to provide anything more in terms of URLs then what the scaffolding provides already.  For characteristic entities recall that only two of the operations required new URLs.  Just adding what is needed is going to make an inconsistent interface so lets extend the pattern to be consistent.  The URL patterns for characteristic entities are;

Operation Method URL Pattern
list GET /<app-name>/<parent-entity>/{entity-id}/<characteristic-entity>
Returns the list of entities that are characteristics of the parent entity with an id of entity-id.

show GET /<app-name>/<parent-entity>/{entity-id}/<characteristic-entity>/{characteristic-entity-id}
Shows the characteristic for the supplied characteristic-entity-id.  Provided for consistency.
Redirects to;

/<app-name>/<characteristic-entity>/{characteristic-entitty-id}

create form GET /<app-name>/<parent-entity>/{entity-id}/<characteristic-entity>?form
Returns a form for creating a new characteristic for the parent entity.

create PUT /<app-name>/<parent-entity>/{entity-id}/<characteristic-entity>
Create a new characteristic for the parent entity.

update form GET /<app-name>/<parent-entity>/{entity-id}/<characteristic-entity>/{characteristic-entity-id}?form
Returns an update form for the characteristic entity.  Redirects to;

/<app-name>/<characteristic-entity>/{characteristic-entity-id}?form

update POST /<app-name>/<parent-entity>/{entity-id}/<characteristic-entity>

Returns the list of entities that are characteristics of the parent entity with an id of entity-id.

delete form GET /<app-name>/<parent-entity>/{entity-id}/<characteristic-entity>
Returns the list of entities that are characteristics of the parent entity with an id of entity-id.

delete DELETE /<app-name>/<parent-entity>/{entity-id}/<characteristic-entity>/{characteristic-entity-id}
Deletes the charachteristic entity with the supplied id.  Redirects to;

/<app-name>/<characteristic-entity>/{characteristic-entity-id}


Then for associative entities we have the following URL patterns;

Operation Method URL Pattern
show GET /<app-name>/<entity-a>/{entity-a-id}/<entity-b>
Returns the list of entity-b's that are associated of entity-a with an id of entity-a-id.

add-form GET /<app-name>/<entity-a>/{entity-a-id}/<entity-b>?form=add

Returns a multi-select form to allow the selection of entity-b entities NOT associated to entity-a with a id of entity-a-id.


add POST /<app-name>/<entity-a>/{entity-a-id}/<entity-b>

Creates one or more association entities between entity-a-id and entity-b ids found in the request body.

delete-form GET /<app-name>/<entity-a>/{entity-a-id}/<entity-b>?form=delete
Returns a form allowing selection of entity-b entities associated with entity-a for deletion.

delete DELETE /<app-name>/<entity-a>/{entity-a-id}/<entity-b>

Deletes one for more association entities between entity-a-id and entity-b ids found in the request body.

We now have a road map to provide consistent restful URL's for Spring Roo based web applications.  The restful URL's that spring provides works well handling independent entities.  By doing some research and analysis we now have a logical and consistent design for the development of restful URL's to manage relations between entities.


1 comment: