links to this page:    
View this PageEdit this PageUploads to this PageHistory of this PageTop of the SwikiRecent ChangesSearch the SwikiHelp Guide
Making a module of Celeste - an incomplete example
Last updated at 5:11 pm UTC on 16 January 2006
I'm not sure I'll finish modularizing Celeste, but whatever happens in the process, will be written here, and people reading it might be less surprised in their own attempts...

The first step is localizing your efforts - Eventually, for the sake of sanity in developing it, Celeste should be composed of various clean modules. But even for the sake of the rest of the system, Celeste should unload cleanly (even as one big messy module).

From reading the basic documentation on refactoring the image, I see that to make a module clean requires cleaning dependencies on it (in order to unload it) and cleaning up its dependencies on things (so it can be loaded easily).

I opened up the ModuleExplorer, traveled over to #(Squeak Network Applications MailReader. I did
self deepIncomingRefsFromOutside: self 
Opening up the explorer appropriately, I get the following

  1. AdHocComposition
    1. ChangeSet>>mailOut
  2. Celeste
    1. TheWorldMenu>>openEmail
    2. MailtoUrl>>composetext
    3. Debugger>>mailOutBugReport
    4. MailtoUrl>>activate
    5. ChangeSet>>mailOut
  3. CelesteComposition
    1. Debugger>>mailOutBugReport
    2. MailtoUrl>>activate
    3. ChangeSet>>mailOut
  4. MailMessage
    1. MIMEDocument>>parts
    2. ChangeSet>>buildMessageForMailOutWithUser:
What does it all mean? we get keys that are classes in my modules, and for each a set of method references outside the module. These methods reference that class, creating a dependency.

From a quick look we can see several things
  1. Some pretty core classes (Debugger, ChangeSet) want to send mail, and they use various classes in MailReader. Also TheWorldMenu knows Celeste like it knows many tools. We'll need to use registries to remove this hardwiring.
  2. Most of those references seem to be reasonable usage of MailReader interfaces, but MIMEDocument>>parts using MailMessage seems a bit backwards. One would expect MailMessage to employ MIMEDocument as a library implementing the MIME encoding, not the other way around... looking at the references to MIMEDocument quickly reinforces this feeling - lots of classes use MIMEDocument, including four methods in MailMessage. This is an ugly cyclic dependency we'll need to clean up somehow.
  3. AdHocComposition isn't supposed to be there! Its purpose in life is to give the ChangeSet mailing functionality to people who don't use Celeste. Obviously, if unloading Celeste removes it, that pretty much misses the point.

Reading more deeply through all those clients of MailReader and how they use them, here're the interface requirements:
  1. We need an interface for sending a single message. This might use Celeste itself or not (using AHC), and information from both Celeste and the client will be used to initialize the message.
  2. We need a way to invoke the UI Celeste (from TheWorldMenu). Generally speaking, TheWorldMenu needs a registry of applications. It's work, but it's straight forward.

That's it, pretty simple really. Some complicating realities
  1. Looking at MIMEDocument shows that it actually needs MailMessage to implement multipart documents. This pair is obviously sick and needs design refactoring someday. OTOH, MailMessage doesn't have to be part of MailReader, so for now, we'll simply move MailMessage from MailReader, deferring that problem too to another iteration.
  2. For sending off a message, the different clients currently do too much work themselves, using too much knowledge about the implementation of MailReader. That's a design refactoring we'll have to consider soon.

So, what do we need to do to move MailMessage away from MailReader?
First, we need to find all the dependencies of MailMessage on MailReader. I did this twice, once by reading the code, and then to make sure I didn't miss anything, I created a variant of #deepIncomingRefsFromOutside: called #deepReferencesFromClass: (hopefully, that's been added to the image).
  Module @ #(Squeak Network Applications MailReader) 
      #deepReferencesFromClass: MailMessage 
yielded the following
  1. MailMessage (methods elided, obviously self references won't create a problem when moving the class)
  2. MIMEHeaderValue
    1. MailMessage>>from:
    2. MailMessage>>makeMultipart
    3. MailMessage>>addAttachmentFrom:withName:
    4. MailMessage>>setField:toString:
  3. TextMessageLink
    1. MailMessage>>bodyTextFormatted
  4. MailDB
    1. MailMessage>>fieldsFrom:do:

MIMEHeaderValue is really part a MailMessage (maybe even of MIMEDocument), so we'll try to move them together.
 Module @ #(Squeak Network Applications MailReader)
  #deepReferencesFromClass: MIMEHeaderValue 
confirms that it won't mind the move.

TextMessageLink implements the nice hyperlinked menu for saving/viewing attachments. They know nothing of Celeste and might be useful in other MIME viewing clients, so maybe we can move them out too? checking its dependencies shows it'll be fine moving too.

MailDB... that's a problem. Looking through #fieldsFrom:do:, we see that it simply uses a very simple utility message of MailDB, that doesn't encapsulate any very important secrets (lines are separated by cr's). We'll inline this method to remove the dependency. This creates a duplication of knowledge, but I don't mind saying that text representations of mail are cr-separated, no matter how the underlying storage holds them. If tommorrow MailDB goes binary, MailDB will just have to hide that from MailMessage.
I inline the method manually (that RefactoringBrowser sure would come in handy, too bad it doesn't yet load into 3.3a, because (last time I checked) DeltaModules don't yet deal with class format changes...). Retest:
 Module @ #(Squeak Network Applications MailReader)
    #deepReferencesFromClass: MailMessage
Nope! MailDB is still there. Oops, the method is used twice. copy the method over to MailMessage, replace MailDB with self, test, this time it's gone.

Now, to move the modules over.
urlMod _ MIMEDocument module.
mailMod _ Module @  #(#Squeak #Network #Applications #MailReader).
mailMod moveName: #MIMEHeaderValue toModule: urlMod.
mailMod moveName: #TextMessageLink toModule: urlMod.
mailMod moveName: #MailMessage toModule: urlMod.

mailMod deepIncomingRefsFromOutside: self 
shows no trace of MailMessage, and no new problems, so that's done.
The new home of MIMEHeaderValue, TextMessageLink and MailMessage is not ideal, but they're arguably no worse than before and can wait for a later iteration.

Next, we'll deal with the clients that wish to send a message. Looking at how the different clients (Debugger, ChangeSet, MailtoUrl, HTTPClient) actually send mails, we see that they use CelesteComposition and its various subclasses. We'll need to make the clients not know how they're sending the mail. Instead, they'll create an appropriate MailMessage and pass it on to MailSender, a registry which knows the different mail sending classes and passes messages to the active one. It appears we'll just have to separate AHC from CelesteComposition sooner rather than later. I don't know how to integrate FancyCelesteComposition into this scheme, and it seems like a hack, so I'll remove it - I'm sure it can be added back later.

What we need to do:
  1. Rename CelesteComposition into MailComposition.
  2. Rename AdHocComposition into CelesteComposition.
  3. Switch between their implmentations of submit.
  4. push down instance variable celeste to CelesteComposition. Actually, celeste is being used by the former AdHocComposition to hold the SMTP server (YUCK!). Replace that with asking MailSender for the address.
  5. Change HTTPClient to create a mail message as per functionality in FancyCelesteComposition.
  6. Create a MailSender class (in a new module outside Celeste) which holds a list of mail sending classes that respond to the protocol #sendMessage:. It considers one of these active at all times and will delegate requests to it.
  7. Make CelesteComposition register/unregister itself in MailSender.
  8. Make MailComposition register/unregister itself in MailSender.
  9. Change all clients to send to MailSender. In the process, I found I have to make the MailSender get a userName too. There's no reason a specific client have it's own idea of the user, so this class variable and related functionality is moved to MailSender.
  10. Remove the class FancyComposition.
  11. Move MailComposition out of Celeste.
Checking for outside references to MailReader after the said changes turns up the following:
  1. Celeste
    1. TheWorldMenu>>openEmail
    2. MailtoUrl>>composeText
It would appear MailtoUrl>>composeText asks Celeste for the ccList. If this is really important to someone, they can add it back, I'm removing it.
Ok, now we're left with only TheWorldMenu.
As I've said before, TheWorldMenu needs a registry of tools to open. I'm not going to decide how that's going to look (enough decisions for today). DoTheSimplestThingThatCouldPossiblyWork - remove the relevant method from TWM and references to it.

Testing, I find out that I need to move also the global variable SMTPServer to MailSender, with related lazy init stuff.

Now, to package up for general consumption. Oops, bad discovery - the big changeset we've made is completely unwilling to load in. Too many class changes and so forth. We'll need to customize a bit here. Here's the idea -
  1. Make a changeset that only changes the client to remove dependencies on existing classes, without fixing the functionality. In its postscript, remove all classes that will remain in MailReader, and move all classes that belong to other Modules.
  2. File out the new modules, creating repositories loadable in the proper order.

In theory, perfect, now if only I could load the darn modules...