I spend some time in the msdn forums on and off (http://forums.microsoft.com/MSDN/) and a couple of days ago a question about contract design in WCF appeared. The idea was to get the most out of the WCF services and handle change sets etc as efficient as possible. This post sums up my ideas and understanding about efficient contract design, there is also a recorded discussion where me and Ron Jacobs addresses the issues in pod-cast form (over here: http://files.skyscrapr.net/users/arcast/rr/ARCastTVRR-20070702-PassingData.mp3)
So to the ideas:
In WCF you really should go the DataContract path and have specified contracts, which you control, what data should be communicated and how. Data contracts has the benefit of giving you alto of options so you can explicitly define your intent.
I would also argue that the data contracts that defines the data that is communicated should be separate from the model you use for accessing data. The Data Contract should define the information flow between services and consumers and not the model used to implement the service itself.
Creating specific data contracts will allow for better service designs and helps you design around the UoW / Versioning issues and will also streamline the information sent over the wire.
For example,
I have a model with questions and answers. To the consumer I want to send complete question information for presentation. But when answering the question there is actually no point in sending full question information back from the consumer to the service to add the answers.
the contracts I use for this scenario looks similar to this:
To get the questions ( public GetSurveyResponse GetDefaultSurvey() {} ):
[DataContract(Namespace = "http://schemas.cornerstone.se/SurveyCollector/GetSurvey")] public class GetSurveyResponse { [DataMember] public Guid Id;
[DataMember] public string Title;
[DataMember] public List Questions = new List(); } [DataContract(Namespace = "http://schemas.cornerstone.se/SurveyCollector/GetSurvey")] public class SurveyQuestionItem { [DataMember] public string Text;
[DataMember] public int Weight;
[DataMember] public QuestionTypes Type;
[DataMember] public Guid Id; }
To send the answers ( public void AnswerSurvey(SurveyAnswerRequest request) {} )
[DataContract(Namespace = "http://schemas.cornerstone.se/SurveyCollector/AnswerSurvey")] public class SurveyAnswerRequest { [DataMember(IsRequired = true)] public string StudentEmail; [DataMember(IsRequired = true)] public string ClassNo; [DataMember] public List Answers; }
[DataContract(Namespace = "http://schemas.cornerstone.se/SurveyCollector/AnswerSurvey")] public class SurveyAnswerItem { [DataMember] public Guid QuestionId; [DataMember] public object Value; }
This guarantees that I just send the information necessary to complete the operation I'm calling.
The original scenario used DAAB as the persistence layer and the discussion went on to talk about how to utilize the data contract together with DAAB and get the data from the contracts into the database:
You will have to move it out of the DataContract objects either by hand or with some nifty O/DataSet mapping code. It's very much the same principles that you apply to your GUI, you don't create a NameTextBox control, you keep the data model and the UI controls separate. The idea is to keep the contracts and the implementation separate to ensure that the implementation don't influence the contracts to much.
This is how it's done in one of the methods of the example contracts in the post earlier (I don't use DAAB for this project, instead the implementation is built in a DDDish style with NHibernate as persistence layer)
public void AnswerSurvey(SurveyAnswerRequest surveyAnswer) { using(SessionScope scope = new SessionScope()) { ISurveyRepository repository = new SurveyRepository(scope); ISurveyFactory factory = new SurveyFactory(repository);
Survey answeredSurvey = factory.CreateFor(surveyAnswer.StudentEmail, surveyAnswer.ClassNo);
foreach (SurveyAnswerItem answerItem in surveyAnswer.Answers) { answeredSurvey.AnswerQuestion(answerItem.QuestionId) .With(answerItem.Value.ToString()); }
repository.Save(answeredSurvey); } }
Udi Dahan (http://www.UdiDahan.com) commented on this as:
"What you have here is not only layers, but tiers - client, app server, and database.
Translation code is good for you. It decouples your layers."
Which is a very nice summary for the need to have your contracts separate from the actual implementation of the WCF service.
The full conversation can be seen here:
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1805743&SiteID=1
|