Archive for the 'LINQ To SQL' Category
NServiceBus - Linq To SQL Saga Persister Part 2.0
In a previous post I showed in part what it would take to create a Saga Persister using LINQ To SQL, however it wasn’t the complete story, therefore I decided on creating two new complete persisters to demonstrate how to use the extensibility points provided by NServiceBus to create a persistence mechanism for Sagas. Notice I said two persisters, one for LINQ To SQL and another for the Entity Framework, this post however deals only the LINQ To SQL Persister.
A Place to Start
I think the best place to start is with some typical configuration code that one might use to initialise the ‘BUS’ for a Saga that is using the LINQ To SQL persister. I have included this code twice, once as text (for copy paste) and another as an image with numbered markers to which I will occasionally refer to.
Listing 1.0
static void Main()
{
LogManager.GetLogger(“hello”).Debug(“Order Started.”);
try
{
const string mappingXml =
@”..\..\..\OrderService.Persistence\SagaL2SMapping.xml”;
const string dbConnectionString =
@”Data Source=BOOMER\BOOM09;” +
“initial catalog=Sagas;user id=sa;”+
“password=supremo”;
var sr = new StreamReader(mappingXml);
var mapping = sr.ReadToEnd();
var loadopts = new DataLoadOptions();
loadopts.LoadWith<OrderSagaData>(o => o.Lines);
var sessionFactory = LinqToSqlSessionFactory
.Configure(mapping, dbConnectionString,
loadopts, new DebuggerTextWriter());
NServiceBus.Configure.With()
.SpringBuilder(
(cfg =>
{
cfg.ConfigureComponent<OrderSagaFinder>
(ComponentCallModelEnum.Singlecall)
.SessionFactory = sessionFactory;
}))
.XmlSerializer()
.MsmqTransport()
.IsTransactional(true)
.PurgeOnStartup(true)
.DbSubscriptionStorage()
.Table(“Subscriptions”)
.SubscriberEndpointColumnName(“SubscriberEndpoint”)
.MessageTypeColumnName(“MessageType”)
.Sagas()
.LinqToSqlSagaPersister<OrderSagaData>(sessionFactory)
.UnicastBus()
.ImpersonateSender(false)
.LoadMessageHandlers(
First<GridInterceptingMessageHandler>
.Then<SagaMessageHandler>()
)
.CreateBus()
.Start();
}
catch (Exception e)
{
LogManager.GetLogger(“hello”).Fatal(“Exiting”, e);
}
Console.Read();
}
Figure 1.0
First up we define a path to the XML mapping file for the entities and a connection string. Yes, I know there are better ways of doing this in production, however I am purposefully making things very plain here for both brevity and clarity. The next thing to note is the use of the LoadOptions class to specify the eager fetching (loading) for any reference data members found in the ISagaEntity implementation for the given Saga. Next up we initialise a LinqToSqlSessionFactory and constructor inject it with it’s dependencies, namely the mapping, connection string, LoadOptions and an optional (can be null), TextWriter for logging SQL output. In order to find Saga’s the NServiceBus infrastructure needs to be configured with a given implementation of the IFindSagas interface, however a ‘Finder’ is not part of the Persister per`se, it does require to be injected with the same mechanism that is responsible for providing a ‘session’ or DataContext in our case. Item 4.0 in Figure 1.0 shows the container “(Spring.Net in this case), configuring our Saga Finder. The last interesting part of the fluent configuration code is the LinqToSqlSagaPersister shown at Item 5.0 in Figure 1.0, which is simply providing the infrastructure the hook to the persister itself. Essentially all that is going on here is that we are providing the NServiceBus infrastructure with what it needs to know to persist a long running services (saga) state, by initialising a Session factory and providing for that Session Factory to be injected into the Saga Persister and Message Module (see page 22) that combine to form all the required moving parts for saga persistence.
The Message Module is an implementation of the NServiceBus IMessageModule will be called at the start and ending of all message handlers and in the case of the Persister, it provides a means to prime and end a Session (DataContext) for the Saga Finder and Persister to manage handle the CRUD aspects of the sagas state.
public class LinqToSqlMessageModule : IMessageModule
{
public void HandleBeginMessage()
{
SessionFactory.GetSession();
}
public void HandleEndMessage()
{
SessionFactory.CloseSession();
}
public virtual LinqToSqlSessionFactory SessionFactory { get; set; }
}
Now for the Persister itself. The Persister implements the ISagaPersister interface(see page 88), and is responsible for (most of) the CRUD operations in managing the persisted state of the Saga. Something to note about the LINQ To SQL Saga Persister that distinguishes itself from the NHibernate Persister that comes out of the box with NServiceBus, is the use of the Generic constructed type. The reason behind the generic is a result of the LINQ To SQL DataContext’s mechanism in retrieving an entity with the GetTable<T>() method.
Listing 2.0
public class LinqToSqlSagaPersister<T> :
ISagaPersister where T : class, ISagaEntity
{
private LinqToSqlSessionFactory _sessionFactory;
public void Complete(ISagaEntity saga)
{
var ctx = _sessionFactory.GetSession();
ctx.GetTable<T>().DeleteOnSubmit(saga as T);
ctx.SubmitChanges();
}
public ISagaEntity Get(Guid sagaId)
{
return SessionFactory.GetSession().GetTable<T>()
.Where(s => s.Id == sagaId).SingleOrDefault();
}
public void Save(ISagaEntity saga)
{
var ctx = _sessionFactory.GetSession();
ctx.GetTable<T>().InsertOnSubmit(saga as T);
ctx.SubmitChanges();
}
public void Update(ISagaEntity saga)
{
var ctx = _sessionFactory.GetSession();
ctx.SubmitChanges();
}
/// <summary>
/// The injected Session Factory.
/// </summary>
public virtual LinqToSqlSessionFactory SessionFactory
{
get { return _sessionFactory; }
set { _sessionFactory = value; }
}
}
Here also is a look at a LINQ To SQL finder implemented for the Manufacturing sample that comes with the NServiceBus download. Notice that it’s setup to help find the Saga by whatever relevant fields constitute our search parameters for the messages in our system.
Listing 3.0
public class OrderSagaFinder :
IFindSagas<OrderSagaData>.Using<OrderMessage>,
IFindSagas<OrderSagaData>.Using<CancelOrderMessage>
{
public OrderSagaData FindBy(OrderMessage message)
{
return FindBy(message.PurchaseOrderNumber,
message.PartnerId);
}
public OrderSagaData FindBy(CancelOrderMessage message)
{
return FindBy(message.PurchaseOrderNumber,
message.PartnerId);
}
public OrderSagaData FindBy(string purchaseOrderNumber, Guid partnerId)
{
return sessionFactory.GetSession()
.GetTable<OrderSagaData>()
.Where(o => o.PurchaseOrderNumber == purchaseOrderNumber &&
o.PartnerId == partnerId)
.SingleOrDefault();
}
private LinqToSqlSessionFactory sessionFactory;
public virtual LinqToSqlSessionFactory SessionFactory
{
get { return sessionFactory; }
set { sessionFactory = value; }
}
}
Figure 2.0 depicts all the pieces that go to making the LINQ To SQL Persisters library including the ConfigureLinqToSqlSagaPersister.
Figure 2.0
The ConfigureLinqToSqlSagaPersister classes responsibility is to configure the the Persister and Message Module and inject the Session Factory (that provides access to the DataContext) used by both – specifically, opening, closing and persisting the saga’s data to the SQL Server database. One of the extensibility points mentioned and exhibited below in Listing 3.0 is the ConfigureLinqToSqlSagaPersister class, which provides the extension method to the NServiceBus Configure class which is in turn a key part of the configuration fluent interface as demonstrated in Listing 1.0.
Listing 3.0
public static class ConfigureLinqToSqlSagaPersister
{
/// <summary>
/// Use the Linq To SQL backed saga persister implementation.
/// Be aware that this implementation deletes sagas that complete
/// so as not to have the database fill up.
/// Requires that a session factory be configured externally and passed in.
/// </summary>
public static Configure LinqToSqlSagaPersister<T>(this Configure config,
LinqToSqlSessionFactory sessionFactory) where T : class, ISagaEntity
{
config.Configurer.ConfigureComponent<LinqToSqlMessageModule>
(ComponentCallModelEnum.Singleton)
.SessionFactory = sessionFactory;
config.Configurer.ConfigureComponent<LinqToSqlSagaPersister<T>>
(ComponentCallModelEnum.Singlecall)
.SessionFactory = sessionFactory;
return config;
}
}
To round it out we have the Session Factory however as you can see from the class diagram it’s fairly self explanatory and I have made all the code available for download from this link so I suggest you check the Session Factory out for yourself from here. The download code includes the Manufacturing sample already setup to use the LINQ To SQL Saga Persister. Please remember to create a database and name it Sagas, before running the TSQL script in the manufacturing example. Also don’t forget to change the config file settings in the OrderServiceHost project, change the SQL Server Database connection strings where applicable and run the TimeoutManager from the NServiceBus Tools directory. One last thing to note (when you build the solutions): the LinqToSqlSagaPersister.dll is the result of an ILMerge of both the persister and config projects.
I will follow tomorrow with a short post on the Entity Framework Persister, however bear in mind that it will only be applicable to Entity Framework 1.0 so be prepared for some comprises due to the lack of POCO support however I will finish the series with a Persister aimed at the Entity Framework version 4.0 sometime shortly thereafter.
6 commentsA Linq To SQL Saga Persister for NServiceBus
NServiceBus is becoming a very popular Open Source development framework and out of the box it supports some very elegant feature extensibility points (thanks Udi) that have been designed very deliberately in a technology agnostic way. This allows us as developers to write our own implementations of certain features in the technology of our choice, and the subject of this post focuses on extending the Saga Persister. However at this point let me just say that it may well be worth your while to read Udi’s post on Saga’s before continuing and then come back here to finish.
The Saga Persister
It may be premature to discuss the Saga Persister without first touching on that which begat it, namely the Saga. I like to think of Saga’s as a long running stateful services, with methods for entry points that handle messages. You can also think of a Saga as a class that contains a set of methods, each one handling a different message type, where the entire Saga, it’s methods and all the logic contained within, accomplish a set of work over time. These methods make up the contract of what is ultimately a stateful service. For a lengthy discussion of Saga’s I recommend reading more from Udi here. In the meantime here’s a somewhat elided example:
public class RequestLeaveSaga : Saga<RequestLeaveSagaData>, ISagaStartedBy<IAnnualLeaveRequest>, IMessageHandler<IAuthorizationForAnnualLeaveResponse>, IMessageHandler<ICancelAnnualLeaveRequest> { public override void Timeout(object state) { //…timeout logic here } public void Handle(IAnnualLeaveRequest message) { Data.Id = Guid.NewGuid(); Data.LeaveRequestId = message.LeaveRequestId; Data.EmployeeId = message.EmployeeId; var leaveRequestForAuthorization = Bus.CreateInstance<IReqeustingAnnualLeaveAuthorization>( m => { m.EmployeeId = message.EmployeeId; m.FirstDayOfLeave = message.LeaveStartsAt; m.NumberOfDaysRequested = Data.CalculateDays(message.LeaveStartsAt, message.LeaveEndsAt); m.TimeOfRequestSubmission = message.TimeOfRequest; m.SagaId = Data.Id; }); Bus.Send(leaveRequestForAuthorization); } public void Handle(IAuthorizationForAnnualLeaveResponse message) { //…handles the response message from a HR authorization service } public void Handle(ICancelAnnualLeaveRequest message) { //…handles cancel message to invalidate the request for leave } }
The key to understanding the Saga Persister is linked directly to the statefulness of the Saga itself, where the job of the persister is to initially hydrate the Saga’s state to storage (persistent or otherwise), so that when one of it’s subsequent message handlers (methods in its contract) are called, it can be re-hydrated from the storage. Saga’s contain a property named Data, which we can see (above) has been referred to in the body of the Handle method for the IAnnualLeaveRequest message. The underlying type for the Data property is determined generically in the signature for Saga<T> where T is an ISagaEntity. When persisting Saga state (to storage), NServiceBus will use the values it finds in the instance of <T> (the ISagaEntity reference in the Data property).
To better understand how this works lets look at the details specified in configuration of the Bus using the NServiceBus fluent interface for configuration, which resides in the process hosting the Saga. The following example is derived from the configuration of the Bus as demonstrated in the NServiceBus “Manufacturing” sample project. In this example (below) I have substituted the “out of the box” persister (backed by NHibernate) with a Linq To SQL implementation.
try { DataContext sessionFactory = GetContext(); var bus = NServiceBus.Configure.With() .SpringBuilder( (cfg => { cfg.ConfigureComponent<OrderSagaFinder> (ComponentCallModelEnum.None) .SessionFactory = sessionFactory; })) .XmlSerializer() .MsmqTransport() .IsTransactional(true) .PurgeOnStartup(false) .DbSubscriptionStorage() .Table(“Subscriptions”) .SubscriberEndpointColumnName(“SubscriberEndpoint”) .MessageTypeColumnName(“MessageType”) .Sagas() .LinqToSqlSagaPersister<OrderSagaData>(sessionFactory) .UnicastBus() .ImpersonateSender(false) .LoadMessageHandlers( First<GridInterceptingMessageHandler> .Then<SagaMessageHandler>() ) .CreateBus() .Start(); } catch (Exception e) { LogManager.GetLogger(“hello”).Fatal(“Exiting”, e); }
There are two main areas of this configuration code that we should focus on. Firstly when we configure the nominated Inversion of Control container we can specify which Saga Finder to have configured and inject the required LINQ To SQL DataContext. Further down in this usage of the fluent interface I have specified that this endpoint supports Sagas and that they should use the LINQ To SQL Saga Persister and also that the same DataContext should be injected.
.Sagas() .LinqToSqlSagaPersister<OrderSagaData>(sessionFactory)
namespace NServiceBus { public static class ConfigureLinqToSqlSagaPersister { public static Configure LinqToSqlSagaPersister<T>(this Configure config, DataContext sessionFactory) where T : class, ISagaEntity { config.Configurer .ConfigureComponent<LinqToSqlSagaPersister<T>> (ComponentCallModelEnum.Singlecall) .SessionFactory = sessionFactory; return config; } } }
Implement an Interface
To create your custom Saga Persister you will need to implement an interface found in the NServiceBus library, unsurprisingly this Interface is named ISagaPersister and has four method signatures that make up it’s contract definition.
public class LinqToSqlSagaPersister<T> : ISagaPersister where T : class, ISagaEntity { public void Complete(ISagaEntity saga) { SessionFactory.GetTable<T>().DeleteOnSubmit(saga as T); SessionFactory.SubmitChanges(); } public ISagaEntity Get(Guid sagaId) { return SessionFactory.GetTable<T>() .Where(s => s.Id == sagaId).Single(); } public void Save(ISagaEntity saga) { SessionFactory.GetTable<T>().InsertOnSubmit(saga as T); SessionFactory.SubmitChanges(); } public void Update(ISagaEntity saga) { SessionFactory.SubmitChanges(); } public virtual DataContext SessionFactory { get; set; } }
The four methods of an ISagaPersister are responsible for interacting with the persistence store by getting a Saga entity by it’s ID, updating an existing saga entity, saving a saga entity and setting a saga as completed and removing it from the active saga list.
The Mapping File
In order to Map the ISagaEntity to the database tables we can take advantage of the fact that LINQ To SQL supports POCO’s and simply map our entities as required.
<?xml version=”1.0″ encoding=”utf-8″?> <Database Name=”Sagas” xmlns=”http://schemas.microsoft.com/linqtosql/mapping/2007″> <Table Name=”dbo.OrderSagaData” Member=”OrderSagaData”> <Type Name=”OrderService.OrderSagaData”> <Column Name=”Id” Member=”Id” DbType=”UniqueIdentifier NOT NULL” IsPrimaryKey=”true” CanBeNull=”false” /> <Column Name=”Originator” Member=”Originator” DbType=”VarChar(50)” CanBeNull=”true” /> <Column Name=”PurchaseOrderNumber” Member=”PurchaseOrderNumber” DbType=”VarChar(50) NOT NULL” CanBeNull=”false” /> <Column Name=”PartnerId” Member=”PartnerId” DbType=”UniqueIdentifier NOT NULL” CanBeNull=”false” /> <Column Name=”ProvideBy” Member=”ProvideBy” DbType=”DateTime NOT NULL” CanBeNull=”false” /> <Association Name=”OrderSagaData_OrderSagaDataLine” Member=”Lines” ThisKey=”Id” OtherKey=”OrderSagaDataId” Storage=”_orderLines”/> </Type> </Table> <Table Name=”dbo.OrderSagaDataLines” Member=”OrderSagaDataLines”> <Type Name=”OrderService.OrderLine”> <Column Name=”Id” Member=”Id” DbType=”UniqueIdentifier NOT NULL” IsPrimaryKey=”true” CanBeNull=”false” /> <Column Name=”OrderSagaDataId” Member=”OrderSagaDataId” DbType=”UniqueIdentifier NOT NULL” CanBeNull=”false” /> <Column Name=”ProductId” Member=”ProductId” DbType=”UniqueIdentifier NOT NULL” CanBeNull=”false” /> <Column Name=”Quantity” Member=”Quantity” DbType=”Float NOT NULL” CanBeNull=”false” /> <Column Name=”AuthorizedQuantity” Member=”AuthorizedQuantity” DbType=”Float NOT NULL” CanBeNull=”false” /> <Association Name=”OrderSagaData_OrderSagaDataLine” Member=”Order” ThisKey=”OrderSagaDataId” OtherKey=”Id” IsForeignKey=”true” Storage=”_order” /> </Type> </Table> </Database>
To make the mapping file work with the DataContext instantiation we need to do something like the following:
private static DataContext GetContext() { StreamReader sr = new StreamReader(“D:\\SagaL2SMapping.xml”); //set the mapping source up XmlMappingSource mapping = XmlMappingSource.FromStream(sr.BaseStream); DataContext db = null; //new up a persistance repository db = new DataContext(“Data Source=BOOMER\\BOOM09;initial “ + “catalog=Sagas;user id=sa;password=supremo”, mapping); var loadopts = new DataLoadOptions(); loadopts.LoadWith<OrderSagaData>(o => o.Lines); db.LoadOptions = loadopts; db.DeferredLoadingEnabled = false; db.Log = new DebuggerTextWriter(); return db; }
The Saga Finder
The final part of the puzzle is the Saga Finder which is dealt with by implementing the class IFindSagas<T>. The IFindSagas class contains a nested interface Using<M> which indicates to NServiceBus that implementers of IFindSagas<T> have the ability to find saga’s of the type <T> where the message being handled is of type <M>. Once again using the “manufacturing” sample from the NServiceBus download, I have implemented a Saga Finder using LINQ To SQL:
public class OrderSagaFinder : IFindSagas<OrderSagaData>.Using<OrderMessage>, IFindSagas<OrderSagaData>.Using<CancelOrderMessage> { public OrderSagaData FindBy(OrderMessage message) { return FindBy(message.PurchaseOrderNumber, message.PartnerId); } public OrderSagaData FindBy(CancelOrderMessage message) { return FindBy(message.PurchaseOrderNumber, message.PartnerId); } public OrderSagaData FindBy(string purchaseOrderNumber, Guid partnerId) { return sessionFactory.GetTable<OrderSagaData>().Where( o => o.PurchaseOrderNumber == purchaseOrderNumber && o.PartnerId == partnerId).FirstOrDefault(); } private DataContext sessionFactory; public virtual DataContext SessionFactory { get { return sessionFactory; } set { sessionFactory = value; } } }
The Using<M> interface in NServiceBus specifies a single method in it’s signature, which you probably guessed is:
T FindBy(M message)
Entity Framework and LINQ To SQL Entities are not Messages!
[OperationContract()] public Customer UpdateCustomer(Customer _customer) { //update the customer here in the database //………………….. return _customer; }
What is it about the customer that should be interesting? Some event regarding a customer surely took place? Perhaps that event indicates to us that we should consider a more descriptive name for the message and a less generic data structure as the message? Surely to update a customer we are necessarily updating everything about that customer? Perhaps UpdatingCustomerPersonalDetails would be more appropriate? If I was wearing my publish and subscribe hat, I might think that this method and service are not alone in having an interest in an UpdatingCustomerPersonalDetails message. Perhaps some other business service is interested in specific changes to the state of a customers details? There has been a bit of talk around of late regarding the role of DTO’s and how often and where data representations should or shouldn’t be duplicated. I personally have been an practicing MVC, MVP kinda guy for quite a long time now and this issue always seemed to get a bit of airplay when discussing ‘what goes in the Model‘ and ‘should Models be shared‘. People would ask, why support data structures of two types at different points in my architecture? Simple answer is dependency / coupling. I have yet to come across an instance that supports the notion that my UI is interested in displaying all my Customer data (customer as an example entity). If my Customer data structures change everyone changes with them, regardless of whether my UI or my business layer is concerned with the change. And, this all naturally leads to the question, is the RPC styled UpdateCustomer() method (above) a demonstration in tight coupling? I would say yes it is a fragile dependency and any change to this shared anaemic artifact will ripple through an architecture. If however we consider for a moment that our services should ‘handle‘ messages where they have a registered or known explicit handler for any given messages, then we can quickly dispense with the whole shared anaemic model. In a previous post I highlighted (in somewhat more detail) this approach below.
public class TestHandler : IMessageHandler<TestMessage> { public void HandleMessage(TestMessage message) { Console.WriteLine(string.Format(“The Test Message name was “ + “{0} and it’s ID is {1}”, message.NameOfMessage, message.ID)); } }
Where do my ORM’s come into this?
So when it comes to the common usage or ‘classic‘ approach with ASMX / WCF Service layers, that follow the anaemic anti pattern as described nicely in our UpdateCustomer() example, you may very well encounter a domain entity defined as either an Entity Framework or LINQ To SQL entity. If you have seen the code generated by Entities in both those technologies, you will have noticed that such entities are decorated with appropriate attributes aimed at supporting serialization over the wire (further encouragement to tread that path). Let’s take our customer as an example in both LINQ To SQL and Entity Framework:
LINQ To SQL
[Table(Name="dbo.Customers")] [DataContract()] public partial class Customer : INotifyPropertyChanging, INotifyPropertyChanged { private string _CustomerID; [Column(Storage="_CustomerID", DbType="NChar(5) NOT NULL", CanBeNull=false, IsPrimaryKey=true)] [DataMember(Order=1)] public string CustomerID { get { return this._CustomerID; } set { if ((this._CustomerID != value)) { this.OnCustomerIDChanging(value); this.SendPropertyChanging(); this._CustomerID = value; this.SendPropertyChanged(“CustomerID”); this.OnCustomerIDChanged(); } } } }
Turning on the Serialization Mode to Unidirectional for the LINQ To SQL DataContext and it will add decorate with a DataContract attribute for Entities and the DataMember attribute for their Properties, so we can easily transport these entities over wire with WCF and re-serialize them at the receiving end.
Entity Framework
[global::System.Data.Objects.DataClasses.
EdmEntityTypeAttribute(NamespaceName="NorthwindModel", Name="Customer")]
[global::System.Runtime.Serialization.DataContractAttribute(IsReference=true)]
[global::System.Serializable()]
public partial class Customer :
global::System.Data.Objects.DataClasses.EntityObject
{
/// <summary>
/// Create a new Customer object.
/// </summary>
/// <param name=”customerID”>Initial value of CustomerID.</param>
/// <param name=”companyName”>Initial value of CompanyName.</param>
public static Customer CreateCustomer(string customerID, string companyName)
{
Customer customer = new Customer();
customer.CustomerID = customerID;
customer.CompanyName = companyName;
return customer;
}
//…..ETC ETC
}
You can see here too that the Entity Framework has setup Customer as a DataContract (ready for WCF wire serialization) and it’s Orders collection shown below is decorated as a DataMember.
[global::System.Data.Objects.DataClasses.
EdmRelationshipNavigationPropertyAttribute
("NorthwindModel", "FK_Orders_Customers", "Orders")]
[global::System.Xml.Serialization.XmlIgnoreAttribute()]
[global::System.Xml.Serialization.SoapIgnoreAttribute()]
[global::System.Runtime.Serialization.DataMemberAttribute()]
public global::System.Data.Objects.DataClasses.EntityCollection<Order> Orders
{
get
{
return
((global::System.Data.Objects.DataClasses.IEntityWithRelationships)
(this)).RelationshipManager.GetRelatedCollection<Order>
(“NorthwindModel.FK_Orders_Customers”, “Orders”);
}
set
{
if ((value != null))
{
((global::System.Data.Objects.DataClasses.IEntityWithRelationships)
(this)).RelationshipManager.InitializeRelatedCollection<Order>
(“NorthwindModel.FK_Orders_Customers”, “Orders”, value);
}
}
}
Both LINQ To SQL and Entity Framework take this approach expressly to enable us in serializing the data representations of entities across the wire for distributed computing. That’s great if you need to return your entities from your web services and all this makes that simple, but not necessarily good practice nor a coupling price I am prepared to pay.
I will confess that I have been guilty of using this pattern in the past, but for now entities are entities, having taken back the behaviors that rightfully belonged to them before being hijacked.
There seems also to have to been some discussion (worth checking out) on this topic and around DTO’s recently in Seattle.
No comments







