Modèles de domaine POCO, DTO, DLL et anémique
j'étais à la recherche à la différences entre POCO et DTO (Il semble que POCO sont dto avec le comportement (méthodes?))et suis tombé sur cet article par Martin Fowler sur l'anémie de modèle de domaine.
par manque de compréhension, je pense que j'ai créé un de ces modèles de domaine anémique.
dans une de mes applications, j'ai mes entités de domaine d'affaires définies dans une dll "dto". Ils ont un beaucoup de propriétés avec getter et setter et pas grand chose d'autre. Mon code logique d'entreprise (peupler, calculer) est dans une autre dll "bll", et mon code d'accès aux données est dans une dll "dal". "Meilleure pratique", j'ai pensé.
donc typiquement je crée un dto comme ça:
dto.BusinessObject bo = new dto.BusinessObject(...)
et le passer à la couche bll comme ainsi:
bll.BusinessObject.Populate(bo);
qui à son tour, exécute une certaine logique et la passe à la couche dal comme ainsi:
dal.BusinessObject.Populate(bo);
d'après ce que j'ai compris, pour transformer mes dto en POCO, j'ai besoin de faire en sorte que la logique commerciale et le comportement (les méthodes) fassent partie de l'objet. Donc au lieu du code ci-dessus il est plus comme:
poco.BusinessObject bo = new poco.BusinessObject(...)
bo.Populate();
ie. J'appelle la méthode sur l'objet plutôt que de passer l'objet à la méthode.
ma question Est - Comment puis-je faire cela et conserver la "meilleure pratique" stratification des préoccupations (dll séparés, etc...). N'appelle pas la méthode sur le objet signifie que la méthode doit être définie dans l'objet?
aidez ma confusion.
3 réponses
typiquement, vous ne voulez pas introduire de persistance dans vos objets de domaine, car il ne fait pas partie de ce modèle d'affaires (un avion ne se construit pas, il transporte passagers/fret d'un endroit à un autre). Vous devez utiliser le motif de dépôt , un ORM framework , ou quelque autre motif d'accès aux données pour gérer le stockage persistant et le replivalent d'un objet état .
Là où le modèle de domaine anémique entre en jeu, c'est quand vous faites des choses comme ça:
IAirplaneService service = ...;
Airplane plane = ...;
service.FlyAirplaneToAirport(plane, "IAD");
dans ce cas, la gestion de l'état de l'avion (qu'il soit en vol, où il se trouve, Quelle est l'Heure de départ/l'aéroport, Quelle est l'heure d'arrivée/l'aéroport, Quel est le plan de vol, etc.) est déléguée à quelque chose d'extérieur à l'avion... l'instance AirplaneService.
une façon POCO de mettre en œuvre ceci serait de concevoir votre interface de cette façon:
Airplane plane = ...;
plane.FlyToAirport("IAD");
c'est plus facile à découvrir, car les développeurs savent où chercher pour faire voler un avion (il suffit de dire à l'avion de le faire). Il vous permet également de vous assurer que l'état est seulement géré en interne. Vous pouvez alors faire des choses comme l'emplacement actuel en lecture seule, et assurez-vous qu'il est seulement changé dans un endroit. Avec un objet de domaine anémique, puisque l'état est réglé extérieurement, découvrir où l'état est modifié devient de plus en plus difficile l'échelle de votre domaine augmente.
je pense que la meilleure façon de clarifier ceci est par définition:
DTO: Objets de Transfert de Données:
ils ne servent que pour le transport de données typiquement entre la couche de présentation et la couche de service. Rien de moins ou de plus. En général, il est implémenté sous forme de classe avec gets et sets.
public class ClientDTO
{
public long Id {get;set;}
public string Name {get;set;}
}
BO: Business Objects:
Business objects represents les éléments d'affaires et naturellement la meilleure pratique disent qu'ils devraient contenir la logique d'affaires aussi. Comme L'a dit Michael Meadows, il est également de bonne pratique d'isoler l'accès aux données de ces objets.
public class Client
{
private long _id;
public long Id
{
get { return _id; }
protected set { _id = value; }
}
protected Client() { }
public Client(string name)
{
this.Name = name;
}
private string _name;
public string Name
{
get { return _name; }
set
{ // Notice that there is business logic inside (name existence checking)
// Persistence is isolated through the IClientDAO interface and a factory
IClientDAO clientDAO = DAOFactory.Instance.Get<IClientDAO>();
if (clientDAO.ExistsClientByName(value))
{
throw new ApplicationException("Another client with same name exists.");
}
_name = value;
}
}
public void CheckIfCanBeRemoved()
{
// Check if there are sales associated to client
if ( DAOFactory.Instance.GetDAO<ISaleDAO>().ExistsSalesFor(this) )
{
string msg = "Client can not be removed, there are sales associated to him/her.";
throw new ApplicationException(msg);
}
}
}
classe de Service ou D'Application Ces classes représentent l'interaction entre L'Utilisateur et le système et elles utiliseront à la fois ClientDTO et Client.
public class ClientRegistration
{
public void Insert(ClientDTO dto)
{
Client client = new Client(dto.Id,dto.Name); /// <-- Business logic inside the constructor
DAOFactory.Instance.Save(client);
}
public void Modify(ClientDTO dto)
{
Client client = DAOFactory.Instance.Get<Client>(dto.Id);
client.Name = dto.Name; // <--- Business logic inside the Name property
DAOFactory.Instance.Save(client);
}
public void Remove(ClientDTO dto)
{
Client client = DAOFactory.Instance.Get<Client>(dto.Id);
client.CheckIfCanBeRemoved() // <--- Business logic here
DAOFactory.Instance.Remove(client);
}
public ClientDTO Retrieve(string name)
{
Client client = DAOFactory.Instance.Get<IClientDAO>().FindByName(name);
if (client == null) { throw new ApplicationException("Client not found."); }
ClientDTO dto = new ClientDTO()
{
Id = client.Id,
Name = client.Name
}
}
}
personnellement, je ne trouve pas ces modèles de domaine anémiques aussi mauvais; j'aime vraiment l'idée d'avoir des objets de domaine qui ne représentent que des données, pas des comportements. Je pense que l'inconvénient majeur de cette approche est l'identification du code, vous devez connaître les actions qui sont disponibles pour l'utilisation. Une façon de contourner cela et de garder le code de comportement découplé du modèle est d'introduire des interfaces pour le comportement:
interface ISomeDomainObjectBehaviour
{
SomeDomainObject Get(int Id);
void Save(SomeDomainObject data);
void Delete(int Id);
}
class SomeDomainObjectSqlBehaviour : ISomeDomainObjectBehaviour
{
SomeDomainObject ISomeDomainObjectBehaviour.Get(int Id)
{
// code to get object from database
}
void ISomeDomainObjectBehaviour.Save(SomeDomainObject data)
{
// code to store object in database
}
void ISomeDomainObjectBehaviour.Delete(int Id)
{
// code to remove object from database
}
}
class SomeDomainObject
{
private ISomeDomainObjectBehaviour _behaviour = null;
public SomeDomainObject(ISomeDomainObjectBehaviour behaviour)
{
}
public int Id { get; set; }
public string Name { get; set; }
public int Size { get; set; }
public void Save()
{
if (_behaviour != null)
{
_behaviour.Save(this);
}
}
// add methods for getting, deleting, ...
}
comme ça tu peux séparez la mise en œuvre du comportement du modèle. L'utilisation des implémentations d'interface qui sont injectés dans le modèle rend également le code plutôt facile à tester, puisque vous pouvez facilement simuler le comportement.