Traduction

Cet article est la traduction la plus fidèle possible de l'article original de Manuel Felicio, Architecting Silverlight LOB applications (Part 3) - Domain Services and Repository.

Architecturer des applications métier Silverlight - Services de domaine et repository

Dans le précédent article, j'ai décrit une vue d'ensemble d'une architecture qui pourrait être utilisée lors du développement d'applications modulaires Silverlight. Dans cet article je vais démarrer avec un modèle de domaine métier simple et je vais créer une couche de domaine autour de ce modèle.

Dans cet exemple notre application sera une application Web pour une société qui vend des produits et qui peut être utilisée par ses clients pour passer des commandes et par ses employés pour les traiter. Ces commandes contiennent des détails qui consistent en des produits et des quantités. Chaque produit peut avoir une catégorie de produit. Quand un client soumet une commande, un employé de la société peut traiter cette commande et préparer les articles pour l'expédition.

La société a quelques départements et chaque employé fait partie d'un département. Cela signifie que les employés du service des ventes devront traiter les commandes ; les employés du département des ressources humaines pourront recruter de nouveaux employés ; et les employés du département des finances pourront observer quelques graphiques et imprimer des rapports avec des statistiques sur comment évoluent les ventes avec le temps, des rapports de ventes, etc.

L'authentification et l'autorisation seront par la suite ajoutées.

Par souci de simplicité, j'ai créé le modèle de données suivant :

Image non disponible

Ce modèle sera tant notre modèle de données que notre modèle de domaine. C'est très simple et comme je l'ai dit dans le précédent article, créer un modèle de domaine séparé est facultatif car vous pouvez toujours bien vous en sortir avec vos entités de données. Puisque nous utilisons Entity Framework nous allons tirer profit de LinqToEntitiesDomainService, un service de domaine spécifique qui traite des objets de l'Entity Framework.

Puisque nous commençons par notre couche de domaine, nous allons définir un service de domaine qui peut être utilisé depuis notre application Silverlight (ou d'autres clients également) et créer les opérations CRUD. Je suis sûr que vous avez remarqué combien de code (dupliqué) est généré lorsque vous utilisez le modèle de service de domaine de Visual Studio. Eh bien… plus besoin de souffrir ! Créez tout simplement une classe de base pour vos services de domaine et ajoutez les méthodes suivantes :

 
Sélectionnez
  1. #region Basic CRUD 
  2. protected IQueryable<T> GetQuery<T>() 
  3.     where T : class 
  4. { 
  5.     return this.GetRepository<T>().GetQuery(); 
  6. } 
  7. protected void Insert<T>(T entity) 
  8.     where T : class 
  9. { 
  10.     this.GetRepository<T>().Add(entity); 
  11. } 
  12. protected void Update<T>(T entity) 
  13.     where T : class 
  14. { 
  15.     this.GetRepository<T>().AttachAsModified(entity, this.ChangeSet.GetOriginal(entity)); 
  16. } 
  17. protected void Delete<T>(T entity) 
  18.     where T : class 
  19. { 
  20.     this.GetRepository<T>().Delete(entity); 
  21. } 
  22. #endregion 
  23.  
  24. protected IRepository<T> GetRepository<T>() 
  25.     where T : class 
  26. { 
  27.     return this.Container.Resolve<IRepository<T>>(); 
  28. } 

La propriété Container est une instance de IUnityContainer, que je garde dans le service de domaine de base. J'ai d'habitude une classe singleton commune où le conteneur IoC est disponible mais je crée aussi un conteneur enfant pour chaque service de domaine. Cela me permet d'enregistrer des instances spécifiques dans le conteneur enfant et de l'utiliser uniquement dans la portée de mon service de domaine. C'est utile pour résoudre des repositories parce que mes repositories dépendent de l'ObjectContext qui est injecté via l'injection de dépendances. Voici comment on procède :

 
Sélectionnez
  1. public abstract class DomainRepositoryService<TContext> : LinqToEntitiesDomainService<TContext>, IDbContextManager 
  2.     where TContext : ObjectContext, IDbContext, new() 
  3. { 
  4.     protected IUnityContainer Container { get; private set; } 
  5.     /// <summary> 
  6.     /// Create a child container to use THIS as the IDbContextManager implementation when resolving repositories 
  7.     /// Ensures that other threads / domain services can use the main container to resolve repositories 
  8.     /// and use its own instance as the IDbContextManager for the repositories they need 
  9.     /// </summary> 
  10.     /// <param name="context">The DomainServiceContext instance</param> 
  11.     public override void Initialize(DomainServiceContext context) 
  12.     { 
  13.         base.Initialize(context); 
  14.  
  15.         this.Container = IoC.Current.Container.CreateChildContainer(); 
  16.         this.Container.RegisterInstance<IDbContextManager>(this); 
  17.     } 
  18.  
  19.     #region IDbContextManager Members 
  20.     public IDbContext GetDbContext() 
  21.     { 
  22.         return this.ObjectContext; 
  23.     } 
  24.     #endregion 
  25.     //... 
  26. } 

Ceci dit, le service de domaine spécifique pour notre application Silverlight ressemblera à ceci :

 
Sélectionnez
  1. [EnableClientAccess()] 
  2. public class MyAppService : DomainRepositoryService<MyAppEntities> 
  3. { 
  4.     #region Customer 
  5.     public IQueryable<Customer> GetCustomers() 
  6.     { 
  7.         return base.GetQuery<Customer>(); 
  8.     } 
  9.     public void InsertCustomer(Customer entity) 
  10.     { 
  11.         base.Insert(entity); 
  12.     } 
  13.     public void UpdateCustomer(Customer entity) 
  14.     { 
  15.         base.Update(entity); 
  16.     } 
  17.     public void DeleteCustomer(Customer entity) 
  18.     { 
  19.         base.Delete(entity); 
  20.     } 
  21.     #endregion 
  22.     //repeat for the other entities... 
  23. } 

Remarquez comment ma couche de domaine ne se soucie pas vraiment de comment les entités sont stockées ou gérées avec EF (Entity Framework). Il délègue tout simplement la responsabilité à un objet qui implémente l'interface IRepository.

Cette interface peut être générique et ressemble souvent à :

 
Sélectionnez
  1. public interface IRepository<TEntity> 
  2.     where TEntity : class 
  3. { 
  4.     TEntity GetEntity(int id); 
  5.     IQueryable<TEntity> GetQuery(); 
  6.     void Add(TEntity entity); 
  7.     void Update(TEntity entity); 
  8.     void Delete(TEntity entity); 
  9.     /// <summary> 
  10.     /// Abstraction on RIA's AttachAsModified extension method 
  11.     /// </summary> 
  12.     /// <param name="current"></param> 
  13.     /// <param name="original"></param> 
  14.     void AttachAsModified(TEntity current, TEntity original = null); 
  15. } 

Pour la simplicité, j'ai inclus la méthode AttachAsModified dans l'IRepository. Cette méthode est importante parce que RIA exige que l'entité en cours de mise à jour soit attachée dans l'ObjectContext. Une autre option serait de créer une interface séparée qui dérive d'IRepository et d'y ajouter la méthode. C'est que l'interface IRepository est un concept générique qui peut être utilisé avec plusieurs technologies d'accès de données.

Notez que dès que vous créez votre service de domaine et exposez vos entités, tout de suite une autre personne/équipe peut commencer à construire l'application utilisant les entités exposées, tandis qu'une autre équipe continue à travailler sur la couche serveur ajoutant des métadonnées et/ou la logique de validation qui sera également disponible pour l'application cliente.

Conclusion

Dans le prochain article, j'aborderai la spécification des métadonnées pour nos entités et cela devrait être suffisant pour la couche côté serveur, pour l'instant.

Liens

Pour terminer, je tenais à dire que tout le code est disponible sur http://code.google.com/p/mfelicio-wordpress sous trunk/2010/SL-MVVM-RIA.

Remerciements

Je tiens ici à remercier Manuel Felicio de m'avoir autorisé à traduire son article.
Je remercie tomlev pour sa relecture technique et ses propositions.
Je remercie également ClaudeLELOUP pour sa relecture orthographique et ses propositions.