Here are a few (very general) thoughts on techniques that I like to use for a complex module or several interrelated modules:
1. If each module control shares a set of common methods and/or properties, I will create a base class that inherits from PortalModuleBase and which implements the common methods and/or properties. All module controls then derive from this base class.
2. By all means use one or more class libraries for the non-UI portions of the module controls, either separately compiled if using WAP or placed in a folder under App_Code if using WSP/dynamic compilation model. If using the latter model, be absolutely certain that your Namepace(ing) is well thought out and if necessary explicitely referenced when declaring object types, etc.
3. If the module controls use more than a few ModuleSettings or TabModuleSettings, define a Config (or what ever you would like to call it) class that encapsulates all of the settings and handles their setting to default values the first time the module(s) is/are added to a page, as well as their loading and setting in both view/edit controls and settings controls. This also makes it much easier to cache the Config class object (usually using ModuleID or UserId as part of the cachekey) and then retrieve it from the cache in a custom user control/server control.
4. I generally use the DNN NavigateUrl and EditUrl in their various overloads to switch between controls primarily for administrative users and use LoadControl to dynamically load various view controls, particularly for non-administrative users. The former is best when needing a larger, less cluttered page for editing and the later less surprising for various views of the same data which would leave the user wondering where did all the other modules on the page go if redirection to alternate view controls was done with the usual NavigateUrl or EditUrl.
5. Don't hesitate to create custom user controls or custom server controls as needed if they will be used in more than one module control. These can be either placed in your module control (.ascx) declaritively or loaded dynamically. I'm really getting to like using the AJAXToolKit's ExtenderControlBase to create a control extender or use ScriptControlBase as appropriate if there will be much client side code.
6. If appropriate, move some of the common processing off to a generic handler (ashx) or WebService (asmx). I'm especially fond of the former for streaming images or other content. As for the latter, I sure wish that it was more convenient to be able to use something like PageMethods in custom server controls. I've been working recently on an image upload and editing control which includes cropping and imagement adjustment capabilities. As one might expect, the time required to perform the image processing via a WebService is about half that of using a client callback.
7. As for folder structure, I create a folder for the module or group of modules under DesktopModules (using a company name prefix like (WESNet_EPrayer) and then create subfolders for shared images, javascript, shared resoruces, each related sub-module, etc. When installing the module, you can direct each file into the appropriate folder using the foldername and path nodes in the dnn manifest file. The blog module (and I suspect the new forum module - but I havn't had time to look at the new code) is one of the best examples of this. If you are using the dynamically compiled module (WSP) model, it is unfortunate that the App_Code folder is not as flexible when it comes to setting up subfolders. As much as I would like to be able to set up a folder structure like App_Code/WESNet/Module1, AppCode/WESNet/Module2, etc. I have not been able to do so. You can, however, set up sub folders such as App_Code/Module1/Components/ImageUtilities, App_Code/Module1/Components/Configuration, etc.
8. When writing your SQL table and stored proceedure definitions, be sure to prefix all table and sproc names with your company name and the module name - something like WENet_EPrayer_GetRequests. This not only avoids name collisions with other modules database objects, but makes it much easier to extract the Create scripts from the development database when preparing your SqlDataProvider files.
Thats enough thoughts for now - let us know if you have further questions.
[Hope this doesn't post twice as I lost my first reply during a changeover to the new forum between the time I started tiyping and hitting submit!]