views:

329

answers:

2

PREAMBLE:

This is by far the longest post I've left here...but I think it's required in this case.

I've had questions about these kinds of things for a long time: how to name assemblies, and how to divide up classes within them.

I'd like to give an example of an application here, with only a bare minimum of classes to demonstrate what I'm trying to understand.

Imagine an application that

  • Accepts client messages, store them in a db, and then later dequeues them to an MTA server.
  • It's a Web application that has both an ASP.NET interface to write a message + attach attachments.
  • There's also a Silverlight client, so the webapp exposes a ClientServices WCF ServiceContract, with one OperationContract (SaveMessage).
  • There's also a Windows client...does the same thing as the Silerlight contract.

OK. that should be enough of a fake scenario to demonstrate my cluelessness.

The above will need the following classes:

  • Message
  • MessageAddress
  • MessageAddressType (an enum with From, To)
  • MessageAddressCollection

  • MessageAttachment

  • MessageAttachmentType
  • MessageAttachmentCollection

  • MessageException

  • MessageAddressFormatException

  • MessageExtensions (static extension for Message)

  • MessageAddressExtensions (static extension for MessageAddress)
  • MessageAttachmentExtensions (static extension for MessageAttachment)

Project.Contract.dll

My first stab at organizing the above into the right assemblies would be observing that Message, MessageAddress, MessageAttachment, the enums needed for its properties (MessageAddressType, MessageAttachmentType) and the collections needed for them(MessageAddressCollection, MessageAttachmentCollection), are all to be marked as [DataContract] so that they can be serialized between the WCF client and the server. Being common to both, I think I would move them into a neutral shared assembly called Contract.

Project.Client.dll

I'll need a Client proxy of the server [ServiceContract], that refs the classes in the Contract.dll.

So now the server, which also refs Project.Contract.dll could now save serialized Messages received from a WCF Client, and save them into a db.

Plugins

Next I would realize that I would like to have these objects be processed server side by 3rd party plugins (eg; a virus checker)...

But plugins should have readonly access (only) to the variables in order to check the variables, and throw errors if they see something they don't like.

So I would think about going back to have Message inherit from IMessageReadOnly ...but where to put that interface?

Project.Interfaces.dll

If I put it in an assembly called Project.Interfaces.dll, this would work for the plugins who could reference that without having a reference to Contracts.dll...but now the client has to reference both Contracts assembly AND Interfaces...doesn't sound like a good direction...

Duplicate Objects

Alternatively, I could have two Messages structures (and duplicate the other MessageAttachment, etc. classes as well)...one for communicating from client to server (in the Contracts.dll), and then use a second ServerMessage/ServerMessageAddress/ServerMessageAddressCollection on the server side, which inherits from IMessageReadOnly, and then it would appear that I am closer to what I want. With duplicate objects, plugins are limited in access, while Server BL, etc. has full access for types relevant to its work, all while the client has different but identical objects... In fact...they I should probably start considering them as non-identical, making it clearer in my head that the objects are just there to talk to clients, ie Contract/Comm objects)...

The Website UI

which brings up ...hum...if there are two different Messages, and they have now different properties...which one is the most appropriate for using to back the ASP.NET forms? The ServerMessage object seems fastest (no mapping going on between types)...but all the logic has already been worked out against client message objects (with different properties and internal logic). So would I use a ClientMessage, and map it to a Servermessage, to keep the various UI logics the same, across different mediums? or should i prefer mapping, and just rewrite the UI validation?

What about the third case, Silverlight...The Contracts assembly was a Full Framework assembly...which Silverlight can't ref (different framework/build mechanism)....so the assembly that i have on the Silverlight side might be exactly the same code, but has to be a different assembly. How does that work out?

What exactly to Consider as DataContract?

Finally...and this is, I swear, near the end of my huge question...what about the pesky extra classes that are not clearly DataContract?

For example, The MessageAddress was a DataContract. Ok. And the enums it exposed are part of it...Makes sense... But if the messageAddress constructor raises a MessageAddressFormatException...is it considered part of the DataContract?

Can there be Classes common to both Server, Client, AND Plugins?

Or is it an exception that is common to BOTH ServerMessageAddress and ClientMessageAddress, so should not be duplicated, and instead be in a Common assembly...so that in the end, the client has to bind to Contracts AND Common? (Didn't we just go down this alley with the Interfaces assembly?)

What about common Base classes/Interfaces?

And should these exceptions have common base classes? for example...ClientMessageAddressException, ServerMessageAddressException, ServerMessageVirusException (from plugin)...should I struggle to get them to -- as best as possible -- all derive from an abstract MessageException...or is there a time when enheritence/reusse just no longer an appropriate goal to strive for?

HUGE THANKS FOR READING THIS FAR.

I'm a developer and on the tech side I can bumble along ok...but these kinds of questions, where I've had to lay out the assemblies, the architecture, myself, leave me hugely perplexed...and lose me SOOOO much time, as I drive myself batty, moving things around from one assembly to another to see which one is the best fit, all while not really certain of what I am doing, and trying to not get circular references...

So -- really -- thanks for listening, and I hope this gets read by people who can describe how to lay out the above cleanly, hopefully expressing how to think my way through it for future projects as well.

+6  A: 
John Saunders
It's worth reading!
Chris Richner
Thanks for the advice.From the title, I had previously imagined it was more about large Frameworks than Applications. That I wouldn't find much relationship with runnning apps, with a Main() entry point, with defined BL, DL, UI, layers or mention of client/server classes and services.But if you say it will...thanks for the heads up to the book, I'll go get it. I'll follow any route that will lead be to getting better at this stuff...PS: Yeah...was a long post. Sorry about that.
Make sure you also take a look at the Microsoft Patterns and Practices site, at http://msdn.microsoft.com/en-us/practices/default.aspx
John Saunders
+3  A: 

As an architect, I've learned that it doesn't pay to get too wrapped up in getting things absolutely perfect the first time, and perfect is subjective. Refactoring, especially moving classes between assemblies, doesn't have too huge a cost. It sounds to me like you're already thinking things through logically and correctly. Here's my opinions on a few of your questions:

Q: Should I have read-only contracts for my data contract classes?

The plugins most-likely shouldn't be aware of your data contracts at all. A virus checker may take a byte array, a spell checker a string and locale, etc. If you're making a general interface layer for the plugins, you should just isolate what's shared to the data specific to the plugin. This will allow you to maximize their reuse. Thus, I think you'll get little payoff on creating interfaces to your data contract structures, which should mostly be dumb bags of data with little logic that are practically interfaces themselves.

Q: Should I use the same data contract classes as my Silverlight app does in my ASP.NET application or use server-side classes directly?

I would go with the client message objects so you can benefit from code reuse. Object creation is fairly cheap, and I'm sure that most of the mapping would be one-to-one. It's not as fast, true, but that won't be the bottleneck in your application.

Q: Where do I put my exception classes?

I would put your example exception classes in the assembly with the data contract, since they are all raised due to contract violations or as a means to communicate errors while fulfilling the contract.

Q: Should the exceptions have common base classes?

I have yet to need to do this, but I don't know your code base as well as you do. My guess is that it will gain you little if anything.

Edit:

You may be overplanning for the future. In my experience, taking a YAGNI approach has allowed us to get the important things done more quickly. Making incremental design changes is preferred to spending valuable time building an elaborate architecture that you might never even benefit from.

Jacob
"Should I use same data contract in website... or use server-side classes directly?"Excellent! So (to generalize one part of the answer) whereas in Web only apps the question rarely comes up, in RIA, and Client/Server apps, where Client objects are needed, the better approach is to change the Web UI layer to use the common Client objects, and map them to Server objects: keeping common validation logic. The alternate (eg: website was started B4 Silverlight came up) is to keep the website using same old server objects, at the cost of duplication the UI logic and validation. Correct?
A: "Exceptions should be in DataContract".To generalize: create Exceptions, enums, etc. where needed. (in this case Client.DataContract). Keeping apples with apples, etc. in common namespaces/assemblies for org sake only, has little/no payoff. One verification, though: Code Reuse/Common interfaces is a very loose goal? For example: IF I need a similar exception raised by plugins (no ref to Client DataContracts) then -- even though Code Reuse/Common interfaces are worthy goals -- have to develop a duplicate Exception, with no common interfaces even possible.
Q: Should I have read-only contracts for my data contract classes?Agreed with your answer. Except...if using server objects, mapped to from Client objects, these CAN have read only interfaces (with limited number of exposed members) passed to plugins? Or do go much further and see data passed to plugins, even in same tier, as being totally separate from server objects...sort of like Plugin Contract/objects that have to be mapped at the plugin boundary? (ie plugin 'client' objects?)And...what about raised Events data? Readonly interface to server objects? or mapped event-only objects?
Jacob: just wanted to say HUUUUGE thanks for answering so far. I think I've been beating myself up trying to reuse code (and debug in one place only, in the hopes of reducing cost) and use common interfaces, having the compiler catch violations early... that I've ended up in a ball of wax, stiffling my own code. Seems duplicating code, mapping between client and server objects, and less global interfaces (ok for server OR client, but not both)...is going to be the new order of the day...would that sound like a productive way to think about it? Again...HUGE thanks!
Jacob
Thanks :-) In the past, I've inherited code that I found stunk, pieced together 'as needed' until it was no longer coherent, but just small bits flying (somehow) in close formation...Would like to believe that I get to a point some day that whom ever comes behind me, doesn't think the same thing about code I've written...And you're right..YAGNI would be a good thing for me to start doing...Relax, take the middle road....etc. Then again, I've been saying that about YOGA too for years...Thanks so much for your help. Really was fantastic.