Skip to main content
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Katie Maher
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Katie Maher

OOPhoto - Domain Modeling By Persona (Round III)

By on
Tags:

The latest OOPhoto application can be experienced here.

The latest OOPhoto code can be seen here.

As you know, I have been struggling to wrap my small, procedural mind around the domain model of OOPhoto, my latest attempt at learning Object Oriented Programming (OOP) in ColdFusion. I started out with what I referred to as the "basic domain objects" and then I moved onto some more service oriented objects. At that point, I just felt like things were getting more and more complicated. I had to stand back and take a deep breath. I think part of the problem I had was that I didn't fully understand what my goal was; I was learning and thinking and writing all at the same time, and that caused me to lose my way a bit. Well, maybe not lose my way, but certainly I was struggling. So finally, Friday afternoon, I put a halt on the domain modeling and took some time to think about the structure of the application itself. As you saw in my previous post, here is what I came up with:


 
 
 

 
OOPhoto - Visualizing The Application Layers - Remote Facade , Service Layer , Domain Objects  
 
 
 

This graphic makes a lot of sense to me, and Brian Kotek seems to agree; and, with the HTML controller and the FLEX controller all calling the same service layer (although FLEX goes through a potential Remote Facade), it allows me to think about my application in a view-agnostic manner - something I was having a lot of trouble with when I thought about it from an HTML-only standpoint.

Now, coming at this from a Service Layer point of view, and taking Peter Bell's advice to define my functionality though the use of personas, I am gonna run through the Interface and list off the functions that need to be available. I don't yet know how the Service Layer will be divided up, or if it will be divided up at all. I figure once the pieces of functionality are codified, the organization will naturally show itself. And, once I have the service layer API in place, I can start to work my way outside-in to model the rest of the domain.

Note: Part of me wants to go with the idea of a GetByFilter() method as Dan Wilson suggested since this is a public-facing layer, not something internal; however, for now, I am still going to start with the more specific touch-points.

So, here we go, from screen to screen. I am not sure how these will all work yet, or what they will return exactly, but first things first.

Screen 1: Home Page

  • GetRecentPhotos( N )
  • GetGalleryByJumpCode( JumpCode )
  • GetPhotoImage( ID, Size )

Screen 2: Search Page

  • GetGalleriesByKeywords( Keywords )
  • GetPhotoImage( ID, Size )

Screen 3: Gallery Detail Page

  • GetGalleryByID( ID )
  • GetPhotoImage( ID, Size )

Screen 4: Photo Detail Page

  • GetPhotoByID( ID )
  • GetPhotoImage( ID, Size )
  • AddComment( PhotoID, Comment )

Screen 5: Gallery Edit Page

  • GetGalleryByID( ID )
  • UploadPhoto( Name, Base64Data )
  • SaveGallery( PhotoGallery )
  • DeleteGalleryByID( ID )

I think that's all there is to it. As you can see, it's a very simple application. But, as I was listing these pieces of functionality out, I realized that I stumbled upon another, very crucial question: If this is an API for third-party services (HTML, FLEX, etc.), can it pass back objects? Or, do I need to pass back simple string and number data. Out of curiosity, I popped over to the Flickr.com API so see what they did. As it turns out, they pass back XML data, not objects.

So, does this mean my service layer has to return XML rather than objects? At first, I thought Yes.... but then I realized, NO - that's exactly what the RemoteFacade is for - taking the objects in-use and transforming them into a format (XML, JSON, plain text, etc.) that can be used by third party systems.

I can't quite tell yet if this complicates things or simplifies them. I guess what I have to do now is think about the service layer ONLY, and think in terms of objects. I must assume that we are in such a world where we can send a receive native objects to the service layer since, after all, some of our controllers (HTML) will be on the same platform. Once that is in place, I can then go back and create a RemoteFacade that communicates between the Service Layer and whatever 3rd party services are required.

Using my above logic, let's take a look at something like the GetPhotoImage() function. This was designed to take a photo ID and an image size and stream back the appropriate binary data (for use in a IMG SRC attribute). But, do I need to do that in my service layer? My Photo object already had the binary data composed within it thanks to the PhotoAsset object - why would I need a separate method to access that information? The answer - I wouldn't; it would be part of my remote facade maybe, or just handled by my controller as part of a second request.

Along the same lines, look at the UploadPhoto() method. This was designed to upload photo data in base64 format. But, again, if we have a Photo that composes a PhotoAsset object that contains binary data, then really, all we need to do is create a new PhotoAsset object, stuff it in the Photo object and then save the Photo object. Right?

Well, maybe not so right. The photo upload is a bit more complicated than that since it is part of a larger process - the photo gallery add/edit page. And, to make it even more complicated, a photo should not be added or deleted to and from a gallery "officially" until the gallery itself is committed. That means that we have to be able to create Photo and PhotoAsset objects that are not strongly tied to any PhotoGallery (association will happen only when the gallery itself is saved). And, when it comes to adding a new gallery, we might have a situation where we have uploaded 15 photos to a gallery that does not, itself, exist yet.

Again, it seems that with every OOP question answered, three more questions pop up in its place. A hugely frustrating process, but at least I think I am making some headway. Let me round out by condensing the list of above into a single list that is a bit more filtered based on the reasoning I have just conducted:

  • CommitGallery( PhotoGallery )
  • DeleteGallery( PhotoGallery )
  • GetGalleryByID( ID )
  • GetGalleryByJumpCode( JumpCode )
  • GetGalleriesByKeywords( Keywords )
  • GetNewPhotoGallery()
  • GetNewPhoto()
  • GetNewComment()
  • GetPhotoByID( ID )
  • GetRecentPhotos( N )

This isn't my final list, but it's what I have so far based on what I know as of now. I added some "GetNew" methods in there to be able to create new instances of objects that I will then need to populate. Not sure if this should be handled by some sort of Factory method, or if this even needs to be part of the service layer (ie. can I just use CreateObject() for such calls rather than going back to the Service Layer)?.

Also, I removed the following methods:

  • AddComment()
  • GetPhotoImage()
  • UploadPhoto( Name, Base64Data )

These have been removed from the Service Layer since they are part of the object-level implementation. They might make sense in the RemoteFacade layer, but we'll cross that bridge when we get to it.

Oh man, I always feel so drained after these posts.

Reader Comments

10 Comments

It seems to me you are making serious progress here, Ben. Rock on!

I can see that you are getting frustrated with this. I would recommend that you narrow your focus temporarily. You are attempting simultaneously to wrap your head around domain modelling, persistance, transactional integrity, and exposing an API to Flex and HTML apps. No wonder your head hurts! :)

Maybe you should just focus on one aspect, say modelling the domain. Pretend you are developing a general photo gallery library to be handed off to other people. When you've got your nouns and verbs in a workable state, move on to other layers.

As you progress, you will inevitably make changes to your existing designs. Even the uber-gurus require a few iterations, even when armed with all the best practices in the world!

I'd be interested in hearing from the OO ninjas who have been commenting on these last few posts: where do *you* start when desigining a system? The public API? The service layer? The model?

15,640 Comments

@David,

I tried starting out with the Domain Modeling. That went ok initially, but then I realized that my biggest problem at the time was I could see where data translation would happen in HTML versions of the site. This was a big indicator of the fact that I didn't know who should be doing what within the system. As such, I felt that I needed to zoom out and see how it all fits together from a bird's eye view. Once I have/had that in place, I felt that it would be easier to zoom in with a sort of divide-and-conquer attack scenario.

I feel like by doing the Service Layer first, I am sort of creating an "outline" for a play. Then once I have that in place, I can go into each chapter and flesh out the details.

I don't want to jump all over the place, so I think I will try to fully get over this communication issue between the different systems.

116 Comments

Ben, yes, a remote proxy can indeed return complex objects, which are supported by web services and by Flex. But again, I wouldn't worry too much about that. It's something the remote proxy can handle. Focus on the HTML app that you're working on for now. If the Controller asks for a Photo object from the Service, great. Then send that on to the View for display. This is just fine.

I'm a bit confused about your comments on the images though. Are you just giving back the path to the image? Or do you mean an actual binary image, i.e. a piece of BLOB kind of data? When it comes to images, I tend to just reference image paths and let the browser render them as it normally does. It avoids a lot of problems and there isn't much benefit to actually manipulating raw binary images.

15,640 Comments

@Brian,

The images are not stored as files - I have decided to try storing them as binary data in the database (never done it before and it felt like one less thing to worry about than have a file system). As such, there needs to be a way to convert that binary data into a CFContent tag. I assume the img SRC attributes would be like:

img src="photo_asset.cfm?photo_id=4534&size=M"

Then, photo_asset.cfm needs to stream the "M" size photo to the client.

Originally, I thought I would need a way in the service to do that. However, I realized that I can just return the Photo object to the HTML controller, which will have access to the PhotoAsset, which can then stream the binary data. This does not need to be a concern of the Service layer.

132 Comments

@Ben

BLOBs are a really quick way to cripple your entire database server moving around large images, and it ties up CF threads sending out large files, possibly over extended periods of time.

Worse yet, it'll balloon the memory usage of the CF process since it requires reading in the entire image into memory every time to sent it to the browser. It doesn't take very many requests for 2MB images to bring a server down. The web server on the other hand can just stream the images off disk, something your database can't do.

This isn't the same as using cfcontent to send a file on disk, then the CF server can stream the file, but if you're selecting the BLOB from the database and then sending it to the client, you'll bring the entire file into memory first.

I'd instead just organize the files on disk by user:

/images/
/user1
/{ID}.jpg
/...
/user2
/{ID}.jpg

Then the database can store the ImageId and the UserId.

15,640 Comments

@Elliott,

You make a really good point. I had thought that not having to deal with a file system meant one less thing that I have to worry about. I had assumed that all image calls would have to go through the "system" as well. I didn't realize that people would just provide links to the image file itself.

This is obviously something I would have done in my procedural world, and maybe I am taking this too far in my understanding of OOP, but providing a link to the image itself, doesn't that require the View to know the underlying implementation of the data persistence?

Although, I suppose you could have the image SRC attribute point to a service layer, which could then CFLocation to the image itself:

img SRC="photo_asset.cfm?id=5&size=M"

... which becomes:

<cflocation url="#images##id#_#size#.jpg" />

... or something like that. You get what I am saying. I suppose that just because I am not storing the binary data in the model, it doesn't mean that the View needs to know about the implementation. And, since this method would encapsulate it, I could always change the implementation without changing my Views (which I am told is one of the advantages of OOP).

140 Comments

@Ben,

Great stuff, really good stuff. One comment, hopefully a clarification, in reaction to this line:

"And, when it comes to adding a new gallery, we might have a situation where we have uploaded 15 photos to a gallery that does not, itself, exist yet."

You've slipped, I think, into a bit of a DB angle again, since the Gallery object instance does, in fact, exist before you ever add any photos ... it has to, since you've defined a circular dependence that the PhotoGalleryService generates Photo objects within the Photos array property of the PhotoGallery. It is true that the gallery has not yet been persisted, but it exists already in the OO model.

Does that make sense or did I misunderstand the reference?

15,640 Comments

@Jfish,

This is the biggest pain point of me right now - this idea of circular references for objects that might not exist yet.

Yes, you are right, I can create a PhotoGallery instance before the PhotoGallery exists in the database. However, because the Photos are being upload using AJAX, it means that I have to be able to persist the Photo instances before I persist my PhotoGallery instance. These actions are happening on two separate page requests.

The only think I can think of doing would be to cache the new, non-persisted PhotoGallery object in the SESSION scope or something based on a unique form ID, and then put the photos in that object rather than send them directly to the Service Layer... and then, when the form is actually submitted, send the now-populated PhotoGallery object to the service layer.

What I don't like about that is now I have to have SESSION scope enabled just to work with OOP? This seems like a weird constraint to me. What if I have a View to this application that cannot handle session maintenance for one reason or another?

I cannot think of any clean way to do this that doesn't regress to a bit more of a procedural mindset.

140 Comments

Aha, now I see the conundrum. The issue is basically a chicken v. egg situation: can I compose a Photo object without a PhotoGallery object? Interesting question; would love to see Brian K or someone more experienced give some insight into that aspect of object composition.

The second issue is equally intriguing: prior to persisting actual data, how do we easily maintain objects within the app, particularly in an world of transient HTML requests? Certainly passing around serialized objects is an option (creating a RemoteService or some such for our HTML just as we might for Flex), but that gets large. Sessions seems much cleaner for this "temporary" persistence, but quite fragile, especially if you ever need to scale to clustered CF servers.

Good questions and looking forward to answers / continuing discussions.

116 Comments

Regarding the use of the session scope and your Gallery/Photo issue, first, what UI could you be using as a front end that wouldn't be able to use your model? Remember that the use of the session scope is encapsulated in the model. Nothing outside ever knows that it is being used at all.

Another option is to allow Photos to be saved before they are actually associated with a PhotoGallery (basically if Photo has a column for GalleryID, that would allow nulls) and then you would populate it at the time you actually save the Gallery. Or you could have a temporary Gallery that is then replaced with a "real" one once they save it. Remember that all of these issues are a result of your decision to let them save Pictures in the UI before they create the Gallery. If that really makes sense and is how you want people to use it, then the implication is that Photos must be able to exist before the Gallery does.

What happens if the user uploads pictures and then their connection dies before they create the Gallery? Are those pictures "lost"?

I totally agree with the thoughts on storing images on the file system vs. in the database. Storing images in the database adds a lot of extra complexity for no real benefit, and several drawbacks (like db server bandwidth for starters). For what you're doing here, trying to learn OO, I don't see any reason to throw this extra layer of needless complexity on top of it. I'd just save the images to a user-specific folder, and then store the path or image name in the db. That's all it should require.

4 Comments

If you store the images as files, you could allow the images to be uploaded with AJAX, and have AJAX return the file name of the uploaded images and post them with your form when creating the gallery. Then gallery creation can move/rename the images to wherever you want them. If the function you call by AJAX stores it's images in one location, then you could run a scheduled task to clean out abandoned images.

I have a resource repository, where each resource "knows" it's filename. Linking to it in the view becomes as simple as a href="/files/#resource.getFileName()#" #resource.getTitle()

15,640 Comments

@Brian,

I never thought about it that way - that the decision to upload photos had other implications on how the code needed to be implemented. That's a good point. I had just assumed that this was a View-related concern, but you are absolutely right.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel