Partie 3 - Services de domaine et repository▲
Dans la partie précédente, 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 cette partie 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 :
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 la partie précédente, 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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
#region Basic CRUD
protected
IQueryable<
T>
GetQuery<
T>(
)
where
T :
class
{
return
this
.
GetRepository<
T>(
).
GetQuery
(
);
}
protected
void
Insert<
T>(
T entity)
where
T :
class
{
this
.
GetRepository<
T>(
).
Add
(
entity);
}
protected
void
Update<
T>(
T entity)
where
T :
class
{
this
.
GetRepository<
T>(
).
AttachAsModified
(
entity,
this
.
ChangeSet.
GetOriginal
(
entity));
}
protected
void
Delete<
T>(
T entity)
where
T :
class
{
this
.
GetRepository<
T>(
).
Delete
(
entity);
}
#endregion
protected
IRepository<
T>
GetRepository<
T>(
)
where
T :
class
{
return
this
.
Container.
Resolve<
IRepository<
T>>(
);
}
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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
public
abstract
class
DomainRepositoryService<
TContext>
:
LinqToEntitiesDomainService<
TContext>,
IDbContextManager
where
TContext :
ObjectContext,
IDbContext,
new
(
)
{
protected
IUnityContainer Container {
get
;
private
set
;
}
///
<
summary
>
/// Create a child container to use THIS as the IDbContextManager implementation when resolving repositories
/// Ensures that other threads / domain services can use the main container to resolve repositories
/// and use its own instance as the IDbContextManager for the repositories they need
///
<
/summary
>
///
<
param
name
=
"context"
>
The DomainServiceContext instance
<
/param
>
public
override
void
Initialize
(
DomainServiceContext context)
{
base
.
Initialize
(
context);
this
.
Container =
IoC.
Current.
Container.
CreateChildContainer
(
);
this
.
Container.
RegisterInstance<
IDbContextManager>(
this
);
}
#region IDbContextManager Members
public
IDbContext GetDbContext
(
)
{
return
this
.
ObjectContext;
}
#endregion
//...
}
Ceci dit, le service de domaine spécifique pour notre application Silverlight ressemblera à ceci :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
[EnableClientAccess()]
public
class
MyAppService :
DomainRepositoryService<
MyAppEntities>
{
#region Customer
public
IQueryable<
Customer>
GetCustomers
(
)
{
return
base
.
GetQuery<
Customer>(
);
}
public
void
InsertCustomer
(
Customer entity)
{
base
.
Insert
(
entity);
}
public
void
UpdateCustomer
(
Customer entity)
{
base
.
Update
(
entity);
}
public
void
DeleteCustomer
(
Customer entity)
{
base
.
Delete
(
entity);
}
#endregion
//repeat for the other entities...
}
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 à :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
public
interface
IRepository<
TEntity>
where
TEntity :
class
{
TEntity GetEntity
(
int
id);
IQueryable<
TEntity>
GetQuery
(
);
void
Add
(
TEntity entity);
void
Update
(
TEntity entity);
void
Delete
(
TEntity entity);
///
<
summary
>
/// Abstraction on RIA's AttachAsModified extension method
///
<
/summary
>
///
<
param
name
=
"current"
><
/param
>
///
<
param
name
=
"original"
><
/param
>
void
AttachAsModified
(
TEntity current,
TEntity original =
null
);
}
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 la prochaine partie, 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.