Domain model for my Grails Contacts project

This project will have the characteristics of Grailscrowd, with a Mashup quality to it and make it easier to find fellow Grails enthusiasts, because I intend to tabulate names by country and then map things on a Yahoo or Google map. I expect to demo it at Skills Matter in London at some point in the future as part of the London Groovy & Grails user group

Working draft:

Domain model

Central to this model is the base class Party which is extended by Person & Organization:

Party

Person

Organization

The next most important artifacts are ContactMethod & ContactMethodTypeEnum

ContactMethod

ContactMethodTypeEnum

I may change the name ‘ContactMethod’, if i come up with a better name, but it’s just that I intend to make this similar to Yahoo’s address book, so you can specify multiple addresses, emails, telephones etc.
I’ll let the user select the ContactMethodTypeEnum from a list. depending on whether the Party is a Person or Organization and I’ve coded lists to return the appropriate enumerations relevant to the specialisation of Party. I will use this to drive the creation of all the ancillary files that ContactMethod is extended by and create a style of web flow or possibly create an Ajaxy form that will expand depending on the selection of a ContactMethod type. As I write this I realise I need to add ‘comments’ as a property on Party too.

Have just been reading Grails ref &  here’s a quote I need to consider:

“Table-per-hierarchy mapping (the default Inheritence Strategy) has a down side in that you cannot have non-nullable properties with inheritance mapping”.

Just to clarify the terminology here, you get wastage in terms of storage, but a gain in terms of performance because there are no table joins. There is a discriminator field on the Contact method class. (table-per-subclass is the other option, with all the joins). So every subclasses attributes must be nullable, because all the subclasses reside in the same table.

The same also holds for Party to Person and Organization.

Here are those classes:

Address

Telephone

Email

InstantMessage

Website

Then we have the LinkedInGroup & Memberships records I’ve blogged about earlier:

LinkedInGroup

LinkedInGroupMember

Then the Employment, showing the association between a Person and an Organization.

Was  a bit disappointed with Grails not handling nested embedded classes nicely. See my original idea on the Grails mailing list.

Employee with Period inside it, then YearAndMonth and duration stuff all encapsulated in Period.

Had to abandon Period idea, extract code and refactor. Consequently, it’s not as O-O as I’d have liked. :-(

Then I hit problems with wacky precision issues on embedded class. Tried size, range and max constraints all to no effect.

Then when I tried using Short for year & Byte for month, there was a constraint compilation issue:

Exception thrown applying constraint [min] to class [class jgf.YearAndMonth] for value [0]: Parameter for constraint [min] of property [year] of class [class jgf.YearAndMonth] must be the same type as property: [java.lang.Short]
	at jgf.YearAndMonth$__clinit__closure1.doCall(YearAndMonth.groovy:11)

Fixed this in Version 11 modification.

Employment (1-71)

Employment (72-106)

Version 10 looked like this (using V11!):

YearAndMonth

Employment schema

Version 11

YearAndMonth v11

Employment schema v11

According to Robert Fischer’s Grails Persistence with GORM & GSQL book, these embedded classes don’t go in the domain folder, but the groovy/src folder, like so:

Project Structure

I’m not sure if the ContactMethodType enum should be there too. But I just copied an example from here, per my comment at the top of the enum code.

Next comes Skill & Competency. It’s a way I intend to later extract meta tags from blogs and implement a Digg style interface. May need to embelish Competency later..

Skill

Competency

I’ve included schema and indices below so you can see how this works. It’s key to use map syntax for belongsTo.

Competency schema

Competency indices

That just leaves us with Country, County, StateProvinceTerritory and Locality (which is data I’ve extracted from LinkedIn for my level one  contacts), and a few other value objects (embedded classes GeoLocation [for lat/long], PersonName – First/Middle/Last. (Added US Specializations in rev 15 below too)

I intend to initally just have geo-coded data at Country and this Locality area, but later if people fill in more specific details pertaining to themselves or user groups or jobs etc, I’ll have a geo-code at the address level with a finer level of precision. Country has the country tel code, like 44 for UK, 1 for US etc. See here.

Country

StateProvinceTerritory

County

Locality

GeoLocation

This block was added in Revision 15: I may end up dropping these specialisations. Not sure…

USCounty

USState

USZip100sRange

USZip

US Zip deserves a special mention because of Natural key tweak. This is updated version of this post I did earlier today.

USZip schema

(end of block for rev 15)

PersonName

Let me know your thoughts where you feel this can be improved upon.

This project will later evolve into a full blown portal, where I will have crawled blogs and Grailsjobs etc. I’ve also dictated the first 100 Grails podcasts and have been building a profile on people in my contacts database…

GORM Constraints:

In preparation for the second draft of this post, I’ve compiled a list links that others might find useful in regard to GORM constraints:

Revision History

Revision2:

  • I added comments property to Party
  • I forgot to add Serializable to LinkedInGroup Member in this model. Updated image to reflect change
  • I added constraints to Geo Location for latitude and longitude. Schema for Country, Locality & ContactMethod impacted by this & have shown how it impacts underlying database schemas
  • Forgot to implement toString on Period updated image. Just added braces for now {}…
  • Employment wouldn’t compile have commented out Period property for now until I look into Joda-Time

Country schema

Locality schema

Contact Method schema

Revision 3:

  • Added constraints to comments property on Party
  • Added url constraint to Website, changed url property from URL to String for validation constraint & added getAsUrl as transient property
  • Added email constraint to Email Domain class

Party schema: comments now longtext in MYSQL

Revision 4:

  • Noted nullable contraint requirement on all properties of subclasses Address, Telephone, Email, Instant Message & Website.
  • Remember nullable vs blank constraint. Nullable is at database level, blank is at the web tier. (Quote from Scott Davis/Jason Rudolph Infoq PDF book).
  • Note from DGG 2nd ed. P166. If I was to take my Name class – the value object (embedded class) and said the christian name property  had a size constraint of 1..15, you still need blank:false otherwise a value of a single space would be accepted.

Revision 5:

  • Noted I have similar issues between Party to Organization and Person.
  • Made change to Name and Organization, using nullable, blank and maxSize Constraints.
  • Ranamed class Name to PersonName to be less ambiguous & make name property on Person class personName too.

Here is updated schema for Party:

Party Schema with 'name' changes and constraints applied

Revision 6:

  • Have just read this in the ref guide. Will probably need to address this later too.
  • Added CustomValidator to Party. Liked DGG 2nd ed P49 best here. Robert what were you thinking when you wrote GORM/G-SQL book here. Incomprehensible! :-)
  • Added ref for country codes to blog & comment in Country source code.
  • Tweaked ContactMethod, Address, Telephone,  Email, InstantMessage, Website, LinkedinGroup, LinkedinGroupMember, Employment Skill, Competency Locality to add constraints for nullable etc
  • TODO: Look at Competency and make it more like LinkedinGroupMembers. Look into Joda-Time… It’s not this :-), but this!

Revision 7:

  • Updated Competency (& LinkedInGroupMember again). Added diagram of schema for table/indices for Competency.
  • Made some more tweaks to some of classes. Went overboard with null properties on Country/Locality before.
  • TODO: Look into Joda-Time article on Developerworks

Revision 8:

  • Added capture method and category properties to my Party Domain Class. I’m going to be importing data from a few sources and I want to be able to filter down the Grails only contacts manually after the import.
    This way I can slice & dice my contacts in a meaningful way and identify any conversion issues easily.
  • Also updated Person/Skill images to go with the Competency file.

Revision 9:

I don’t think Joda-Time is what I’m after.
I remember reading somewhere, in on of my books, the server-date is not a good choice for checking dates. Trying to track that down.
As I did this came across interesting reference in GinA P493 to TimeCategory.
Also experienced issues with Grails being able to handle nested embedded classes to an arbitrary depth.
I had Employment with Period, which in turn had YearAndMonth (times two) and Duration.
Have had to do away with class Period and move YearAndMonth into Employment for from/to.
Wasn’t all that sure if duration which basically calculated months between from/to could be both transient and embedded. Refactoring method into Employment too and will just implement as a method.
Want to allow year/month to be zero to represent – current year/month for to, so Duration can be calculated relative to current year/month.
From period can not be zero…
Will post update once I track down the illusive Date validation article I read about & will leave a reference to it on the blog.

Revision 10:

  • Have done the refactoring mentioned above.
  • Found Grails came up short with calculating correct level of precision for embedded from/to YearAndMonth properties. year should be 0-2100, month 0-12.
  • I’m using zero a special instance for present which gets interpreted as current year/month. Have included lots of helper methods to facilitate this.
  • If I specify Byte and Short in the Domain class, the size, range or max properties cause issues. I think they are only for use with Integers maybe. Needs confirmation on mailing list.
  • See this post.
  • As I posted the thing to Nabble, I spotted a flaw in the toString() method, so be sure to pay more attention to the one in my blog.
  • Updated Domain model and project structure to reflect the refactored project. Will probably work on Address a bit. Don’t like CountyStateProvince.. You have both a County & a State in US. Will possibly add State/Province/County tables for UK, US, Australia, as that’s what I’m most familiar with geographically..

Revision 11:

  • Finally got the precision of the numeric data year and month so it was recognised in the Employment schema. It involved casting the constraints to Short/Byte.

Revision 12:

  • Updating Address, Locality and Country domain classes. Added StateProvinceTerritory class.
  • Made Country owner of Locality, County and StateProvinceTerritory. (Need to add Geo Location to County & StateProvinceTerritory).
  • Hope someone will come up with solution for my “Key Redirect” post on the Grails mailing list. I want to refactor address to use common Closure. See comment in Address source.
  • I may also do something for State/Zip Ranges in US and telephone dialing codes. All this stuff helps when I try and geo-code contacts to a finer level of precision. Such as area code 310 for Los Angeles etc.
  • After that, I will write a few more Groovy scripts to crawl Wiki for counties of US, & UK, tel exchanges etc and start posting new entries on that.

Revision 13:

  • Have been adding Geo Locations…
  • Found out an interesting titbit of information. Be careful how you assign nullable when you use embedded classes.
  • I wanted Geo-location to be nullable on the Address only, but all other places that exists, like Locality, County, Country, CityStateProvince, I wanted it to not be null.
  • You should not place nullable on embedded class properties, but instead specify it at the embedded level of the containing class. Otherwise it will stick to being nullable across the board if you had assigned the properties this way in lowest class.
  • It’s an odd one though, since zero degrees would be the equator, but I think that has a different meaning for numeric BigDecimal, which translates to decimal(8,6) in MySQL when scale constraint is applied to it. So zero and null should be different if my intuition is correct. Will have to try this out at time I start writing my tests.

Revision 14:

Just got feedback on the Key Redirect post. It turns out all I needed to do, was make my closure static, in order to reference the same closure for State, County and Locality. Many thanks for the tip. Kudos to Burt Beckwith.

Revision 15:

  • Added USCounty & USState as subclasses of County and StateProvinceTerritory. Made them work so the Country must be instance of name ‘United States’. Don’t know if there is a clearer or better way to do this, only I wanted other US specific tables to hang together and not be related to for example UK counties. Would appreciate comments from others on this. I may go back to adding custom validations on base classes and drop USCounty/USState.. So:
    • USState has children USZip100sRange (see here for basis), and USCounties.
    • USZip, references USCounty.
  • Updated Domain model diagram and project structure.
  • Still have North American telephone area codes to add. This is where it gets odd, because Canada, US, Mexico etc work off same system. [1 2]. Most other countries do their own thing as far as I’m aware. I know UK phone system real well & Wiki is great resource for modelling this stuff. All this stuff ties back to Mashup & ways of geographically representing people by whatever means I have at my disposal.
  • Found good article on Phone numbers with global perspective on Wiki.
  • Will then go on to UK (Postcodes [1 2 3] Tel Exchanges [1 2]), Canada & Aussie stuff based on associated links on this line.
  • Footnote. I was aware that one of the Grails plug-ins (Postcode) already UK postcode information and makes use of Nearby.org’s postcode data (minus last two letters of post code). But I also noticed on Wiki a quote that this month (1st April 2010) Ordnance Survey had a free postcode database (Costs & public availability section citation #5) you can download too with full precision, called Code-Point. It’s a free 20MB download! Cool! The download contains one CSV per postcode area and the record layout is here. There’s a bit of a sting in the tail with this data. You don’t get latitude and longitude. Instead you get Eastings and Northings (Columns K/L respectively with postcode in column A). There is a Code Point user guide that shows the fields in chapter 5 on P38. You can get the PDF guide here. There is a spreadsheet that contains formulas for conversions you can get here and a Co-ordinate transformation test page here. Drat the Excel Macros are done in VBScript that won’t work on Excel 2008 for the Mac! Found PDF with equation to do conversion here. Yikes. Last time I did Maths this complex I was doing my A Levels. (or reading Collective Intelligence in Action!) :-)
Advertisements

About this entry