2010-09-29 44 views
4

Contexte: Création d'une application client intelligente sur la plate-forme .NET où vous avez un modèle de base de données complexe avec un nombre élevé de colonnes impliquées. Le style d'application naturel est un CRUD basé sur des données typiques. Dans certains cas, il y a aussi un peu de logique côté serveur et des validations quelque peu complexes. Vous avez le contrôle total du client et du serveur, de sorte que le besoin d'interopérabilité est au minimum.


Cette question a beaucoup de détails, des excuses pour cela, mais c'est parce que je veux mettre le bon contexte pour les réponses.


A quelques hypothèses
- Comme il est pas rare dans le monde Microsoft, la plupart des applications précédentes ont été écrites avec des jeux de données, il est donc la technologie la plus connue pour les développeurs impliqués. Mais disons que les développeurs connaissent bien la philosophie OO.
- Vous devrez exécuter des validations sur le client et le serveur.
- Vous ne montrez pas la plupart des données sous forme de tableau.
- Ce n'est pas une application intranet, donc vous ne pouvez pas assumer trop de la bande passante


La plus grande question: datasets ou des objets?Questions d'architecture d'application distribuée basée sur les données .NET de type CRUD


Si vous optez pour des ensembles de données que vous avez quelques points positifs et négatifs
- En termes de points positifs: Vous obtenez un peu de soutien de Microsoft en termes d'obtention des données de la base de données, obtenir les données sur le réseau et renvoyant les données modifiées sur le réseau en plus petits morceaux - puisque vous ne pouvez spécifier que pour envoyer des modifications. Envoyer moins de données est une bonne chose car il y a potentiellement un peu de données en jeu.
- Les négatifs sont: En termes de validation, de logique métier et ainsi de suite, vous obtenez une forme procédurale de code et vous ne bénéficiez pas des avantages du code orienté objet - comportement et données ensemble, un style plus naturel de travail et penser à ce que vous faites, et peut-être des liens plus étroits avec la logique de validation. Vous pouvez également détourner l'attention de l'avantage de placer l'ensemble de données dans une grille, puisque ce n'est pas le cas d'utilisation courant.

Si vous optez pour des objets, il est le même exercice, mais il y a plus d'options concernées:
Positifs: Comportement et données ensemble. Logique de validation plus proche. Plus facile de voir et de comprendre les relations entre les objets. Code plus lisible. Plus facile à tester unitaire. Mais il y a assez peu de choix et vous travaillez besoin de faire aussi bien:


OU/Cartographie
- Obtenir les données du modèle relationnel aux objets. Les OR-mappers ne sont pas si complexes et seront capables de le gérer correctement. Mais cela ajoute au temps de développement.


cartographie du contrat
- Il est généralement une bonne pratique de données cartographiques à partir d'objets côté serveur pour contracter des objets, DTO probablement pour. Comme il s'agit d'une application qui convient bien à l'architecture de style CRUD, les DTO n'ajoutent pas vraiment de valeur à l'image, mais simplement au travail de mappage.


code partagé
- Vous pouvez aller pour un scénario de code partagé, où l'assemblage avec les données de domaine et la logique est disponible sur le client et côté serveur. C'est un couplage serré, mais ce n'est pas nécessairement mauvais quand vous avez une application client-serveur naturellement couplée. Que vous choisissiez d'ajouter une couche de contrat ou non, vous devez envoyer des structures d'objets volumineuses Étant donné que nous contrôlons le client et le serveur, le transport et l'encodage doivent être codés en binaire sur TCP. Ça va aider. Avec les ensembles de données, vous avez la possibilité de renvoyer uniquement les modifications. Envoyer la structure entière de l'objet dans les deux sens est un problème de performance probable. Une option permettant d'envoyer la structure entière de l'objet consiste à identifier les modifications impliquées (Créer, Mettre à jour, Supprimer) et à envoyer uniquement des informations à ce sujet. En théorie, il n'est pas trop difficile d'envoyer l'ID de la racine agrégée au serveur ainsi que les changements, demander au serveur de charger paresseux la racine de l'agrégat, effectuer les modifications apportées, puis sauvegarder à nouveau. Mais la grande complexité impliquée est d'identifier les changements effectués. Avez-vous déjà choisi cette approche? Pourquoi? Comment faites-vous exactement cela?

Présentation
La technologie de l'interface utilisateur exacte est pas vraiment important pour la question, WinForms, Silverlight ou WPF est possible. Supposons que nous utilisons WPF puisqu'il s'agit d'un nouveau client intelligent. Cela signifie que nous avons une liaison bidirectionnelle et que nous pouvons utiliser MVVM correctement.

Les objets liés à dans l'interface utilisateur devront implémenter INotifyPropertyChanged et déclencher un événement chaque fois qu'une propriété est mise à jour. Comment résolvez-vous cela? Si vous optez pour le scénario de code partagé, vous pouvez l'ajouter aux objets de domaine, mais cela impliquera d'ajouter du code et de la logique du côté du serveur qui n'est jamais censé être utilisé ici. La séparation est plus naturelle si vous optez pour des objets de contrat, mais ce n'est pas beaucoup de valeur ajoutée juste pour ajouter une couche de mapping.

Technologies
Il y a quelques technologies disponibles qui peuvent aider à résoudre certains des problèmes, mais qui compliquent souvent les autres. Les utilisez-vous ou construisez-vous des choses à partir de rien?
**
- CSLA est possible, mais il rend les tests unitaires plus difficiles et semble ajouter un couplage plus étroit à l'accès aux données. Cela aide avec un certain nombre de problèmes, mais personnellement je n'ai aucune compétence avec cette technologie, donc si c'est un bon ajustement est un peu difficile à dire.
- WCF RIA Services serait possible pour une solution Silverlight, mais il y a certainement des limites impliquées. La taille des données est une.
- WCF Data Services est une autre approche pour obtenir quelque chose rapidement, mais REST n'est pas beaucoup d'aide, et vous n'avez pas non plus le support de la validation que vous avez dans les services RIA.

Résumé
Si vous êtes arrivé jusqu'ici, j'espère que vous avez une idée de l'endroit où je veux en venir. J'ai essayé de le réduire pour éviter de parler de tout en même temps, mais le développement distribué est complexe, donc vous devez considérer beaucoup de parties.


Mise à jour

Merci pour les réponses les gars! J'essayais de poser la question assez ouverte pour ouvrir des réponses variables, mais assez spécifique pour répondre à quelques exigences non inhabituelles.

Il existe différents facteurs qui ont des avantages et des inconvénients différents et qui varient d'un système à l'autre. Chacun ajoute habituellement à la complexité de trouver une solution. Un des points de cette question était d'obtenir des réponses en particulier avec quelques exigences supplémentaires qui ne correspondent pas nécessairement directement à la réponse qui est souvent la bonne aujourd'hui - avec une interface utilisateur basée sur les tâches. Je ne suis pas un "gars CRUD", si vous voulez. Mais quelques systèmes, pour diverses raisons (le plus souvent l'héritage), ont un bon ajustement pour CRUD.

De nombreuses applications d'entreprise ont des exigences similaires qui tirent dans des directions différentes:

affaires liées
- Vue: données Affichage à l'utilisateur et mettre à jour les mêmes données (Reads et CUDS - Créer, Update, Delete)
- validation: règles métier

UI liées
- validation: règles de l'interface utilisateur
- mises à jour de l'interface utilisateur: code spécifique pour obtenir simplement l'interface utilisateur de mettre à jour sur les changements d'objet (INotifyPropertyChanged)

liés au réseau
- Taille des données: La quantité de données que vous envoyez sur le fil

DB liée
- Lazy chargement

SRP/réutilisation liée
- Cartographie: Causé par de multiples couches d'objets/séparation des préoccupations

Maintenance/modification
- Modifications: Ajout de nouvelles informations (colonnes/champs)
- Montant du Code
- Réutiliser et "raisons de changer"

limitions techniques
- Suivi des modifications

Mais ce ne sont que quelques très spécifiques. Vous devez toujours savoir quelles sont les «capacités» que vous trouvez les plus importantes et, par conséquent, quel degré d'évolutivité, de disponibilité, d'extensibilité, d'interopérabilité, d'utilisabilité, de maintenabilité et de testabilité vous avez besoin.

Si je voudrais essayer de généraliser quelque chose pour la plupart des situations, je dirais quelque chose comme:

client
- Utilisation MVVM pour la séparation et la testabilité
- Création de la machine virtuelle sur le dessus de DTO
- Mettre en oeuvre INotifyPropertyChanged dans la machine virtuelle.
- L'utilisation XamlPowerToys, PostSharp ou d'autres moyens d'aider à cela peut être utile
- séparé Lit et CUDS dans l'interface utilisateur
- Faire tâche CUDS base, et utiliser des commandes ou similaires à envoyer ces opérations sur le côté serveur

serveur
- sur mesure faire un DTO par écran
- OU utiliser l'approche multi-requête décrite par Ayende dans http://msdn.microsoft.com/en-us/magazine/ff796225.aspx
- Utilisez AutoMapping pour éviter le fastidieux, manuel et sans aucun rapport avec le problème que vous essayez de résoudre l'étape, cette cartographie est
- Que le modèle de domaine soit concerné par les opérations commerciales principalement, y compris les opérations liées CUD, et lit pas
- Évitez réutilisabilité qui ajoute au nombre de raisons de changer
- d'éviter les problèmes d'encapsulation
- (Et que permettre à l'architecture de style CQRS et peut-être mise à l'échelle séparée de lit et CUDS dans le temps)
- Essayez de trouver une approche de validation qui correspond bien à ce qui doit être fait (bonne lecture: http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/02/15/validation-in-a-ddd-world.aspx)

est-ce neccessarily l'approche que je prendrais dans cette situation particulière? Eh bien, c'est ce que je voulais lancer une discussion sur :) Mais il semble que c'était plus difficile que je l'espérais (en dehors de vous deux).

Répondre

1

Problème intéressant :)

Si vous commencez avec quelques principes:

  • Essayez de réduire la quantité de données envoyées sur le fil
  • Essayez de réduire la quantité de temps passé par écrit code de plomberie
  • Essayez d'améliorer la testabilité

sur la base de ce que je voudrais:

  • Utilisez des objets POCO pour transférer des données. DataSets comprennent beaucoup d'informations que vous ne pouvez pas besoin
  • Utiliser Entity Framework POCO pour l'accès base de données, vous permet d'économiser la cartographie de contrat objets aux objets de données
  • Lieu de validation dans les classes d'aide, facile à tester, et prend en charge le modèle de code partagé

Dans nos projets, nous avons économisé du temps en utilisant Entity Framework par rapport à Enterprise Library et DataSets.

Sur le côté serveur et les objets côté client, vous pouvez essayer:

  • L'objet côté client hérite l'objet côté serveur et mettre en œuvre INotifyPropertyChanged
  • Placez l'objet côté côté client et serveur dll séparée est de cette façon il n'y a pas de code inutilisé sur le serveur
  • utilisez Automapper pour mapper entre les deux types.(peut être une meilleure façon d'utiliser les interfaces)
+0

Je ne suis certainement pas en désaccord avec les principes :) Mais surtout la partie sur le fait d'avoir un peu de données est censée faire partie de ce problème. Oui, les ensembles de données incluent l'envoi de plus de données qu'un modèle d'objet, et personnellement je n'aime pas aller le jeu de données, mais il a une fonction pratique de seulement envoyer les changements après un changement (= beaucoup moins de données). Non pas que ce soit la fonction de tueur, mais quelque chose de similaire serait bien pour un modèle d'objet où aller de la manière des commandes n'est pas vraiment une option –

+0

Vous pourriez essayer les entités de suivi automatique dans Entity framework http://blogs.msdn.com/b /efdesign/archive/2009/03/24/self-tracking-entities-in-the-entity-framework.aspx, il s'agit d'un terrain d'entente entre DTO et DataSets. –

7

Je ne peux répondre que de notre propre expérience. Nous avons essayé différents cadres (WCF RIA, Ideblade) et avons conclu que les cadres ne feront qu'empirer les choses. Je vais expliquer plus bas.

Tout d'abord vous devriez oublier CRUD. Seules les applications de démonstration ont CRUD - les applications du monde réel ont un comportement.

Je ne recommande pas d'imiter l'ensemble du graphe d'entité du côté client. Ce sont deux préoccupations séparées.

Vous devez créer des Dto personnalisés pour chaque contexte. Par exemple. Disons que vous avez un OrderSearchView, alors vous allez créer un OrderSearchDto et mappez uniquement les champs dont vous avez besoin. Dans EditOrderView, vous utiliseriez plutôt un EditOrderDto - qui contient uniquement les champs dont vous avez besoin.

Je ne recommanderais pas vraiment d'utiliser un outil de mappage automatique entre les entités et les dto. Parce qu'il n'y a souvent pas de relation un-à-un entre le dto et l'entité. Le dto serait souvent construit par différentes entités backend multiples. La cartographie est si facile de toute façon, donc je ne vois pas le point avec un cadre de cartographie. Et le travail n'est pas la cartographie - c'est écrire le test unitaire - que vous auriez à faire de toute façon (avec ou sans cadre de cartographie).

Dtos devrait être indifférent à la technologie côté client. Et l'implémentation de INotifyPropertyChanged sur le dto enfreint le principe de responsabilité unique. Il y a une réserve, ils sont appelés des objets de transfert de données. Au lieu de cela, vous créez des présentateurs du côté client. Vous créez un EditOrderPresenter qui est un wrapper autour de EditOrderDto. Donc, le dto sera juste un champ de membre privé dans le EditOrderPresenter. Le Presenter est conçu pour être édité dans la couche client - donc il implémenterait généralement INotifyPropertyChanged. Le EditOrderPresenter aurait généralement les mêmes noms de propriétés que le dto.

Vous devez séparer physiquement la validation du client de la validation de l'entité côté serveur. Méfiez-vous du partage! Je pense que la validation du client est juste un ajustement de l'interface graphique - pour améliorer l'expérience de l'interface graphique. Ne pas faire un gros point d'avoir partagé le code de validation entre dto et entité - il pourrait causer plus de maux de tête que d'utilité. Assurez-vous de toujours valider sur le serveur, quel que soit le type de validation effectué côté client. Il existe deux types de validations: la validation de la propriété simple et la validation de l'entité entière (idem pour dto). La validation d'entité ne devrait être effectuée qu'à la transition d'état. Découvrez Jimmy Nilssons Domain Driven Design pour les connaissances de base. Je ne recommanderais pas d'utiliser un moteur de règles de validation - il suffit d'utiliser le modèle d'état.

Alors qu'en est-il des mises à jour, insérer, supprime? Dans nos implémentations, nous utilisons WCF, et l'API WCF n'a qu'une seule méthode: IResponse [] Process (demandes params IRequest []); Qu'est-ce que cela signifie vraiment? Cela signifie que le client envoie un lot de demandes au serveur. Sur le côté serveur, vous implémentez RequestHandler pour chaque requête définie dans le système. Ensuite, vous renvoyez une liste de réponses. Assurez-vous que la méthode Process() est une unité de travail (~ une transaction). Cela signifie que si l'une des demandes du lot échoue - toutes échoueront - et que cela provoquerait une annulation de la transaction - et qu'aucun dommage n'est causé à la base de données. (N'utilisez pas les codes d'erreur dans les gestionnaires de réponse - jetez l'exception à la place.)

Je vous recommande de consulter le serveur de messagerie Agatha. Davy Brion a d'excellents blogs sur la couche de messagerie. Dans notre entreprise, nous avons choisi d'implémenter notre propre serveur de messagerie - parce que nous n'avions pas besoin de tout ce qu'Agatha nous proposait pour améliorer la syntaxe. Quoi qu'il en soit, mettre en place un serveur de messagerie n'est pas vraiment difficile - et c'est une expérience d'apprentissage agréable.Lien http://davybrion.com/blog/

Alors, que faites-vous avec les Dto. Bien, vous ne les mettez jamais à jour, mais vous les changez du côté du client afin d'obtenir la rétroaction appropriée au GUI. Donc, vous faites en sorte que les présentateurs suivent tout ce qui arrive au dto (reqest) - dans le bon ordre. Ce sera votre requestBatch. Ensuite, envoyez le requestbatch à la commande de processus sur WCF - ensuite les requêtes seront "rejouées" sur le serveur et traitées par les gestionnaires de requêtes. Cela signifie en fait que vous ne mettez jamais à jour les dto. Mais les présentateurs pourraient éditer le dto sur le client afin de donner une bonne rétroaction gui. Le travail des présentateurs consiste également à garder une trace de toutes les modifications effectuées, afin de les renvoyer au serveur en tant que requestbatch (avec les requets dans le même ordre qu'ils sont édités). Pensez au scénario suivant, vous récupérez un ordre existant, vous modifiez, puis vous validez les modifications sur la base de données. Cela résulterait en deux lots, un pour l'obtention de la commande et un pour la validation des modifications.
RequestBatch 1: GetOrderByIdRequest

(..then utilisateur édite les données ..)

ReqeuestBatch 2:
StartEditOrderRequest, de changement d'état pour modifier fonctionnement, validation détendue

AddConsigneeToOrderRequest ChangeEarliestETDOnOrderRequest, pas besoin de validation pour les derniers ETD encore!
DeleteOrderlineRequest
ChangeNumberOfUnitsOnOrderlineRequest
EndEditOrderRequest, changement d'état à l'état initial, effectuer la validation des entités ici!
GetOrderByIdRequest, afin de mettre à jour le GUI avec les dernières modifications.

Sur le côté nous utilisons NHibernate. Nhibernate utilise le cache de premier niveau pour éviter une lourde charge de db. Ainsi, toutes les requêtes dans la même unité de travail (requestbatch) utiliseront le cache.

Chaque requête ne doit contenir que la quantité minimale de données requise pour le travail. Cela signifie utiliser le OrderId + quelques autres propriétés au lieu du dto entier. En ce qui concerne la mise à jour optimiste, vous pouvez envoyer certains des anciensValeurs avec la demande - c'est ce qu'on appelle le jeu d'accès concurrentiel. Rappelez-vous que l'ensemble de concurrence ne contient généralement pas beaucoup de champs. Parce que la mise à jour de l'ordre qui a été modifié entre-temps ne signifie pas nécessairement que vous aurez une condition de relance. Par exemple. l'ajout et la ligne de commande tandis qu'entre temps un destinataire a été édité par un autre utilisateur ne signifie pas que vous avez une condition de relance.

Eh bien, cela ne mène pas à beaucoup de travail. Vous aurez certainement beaucoup plus de cours, mais chaque classe sera petite et aura une seule responsabilité.

Nous avons essayé les services WCF RIA dans un projet de taille moyenne. Et ça ne s'est pas très bien passé. Nous avons dû trouver des moyens (hacks) autour du cadre pour faire ce que nous voulions. Et c'est aussi basé sur la génération de code - ce qui est assez mauvais pour un serveur de build. En outre, vous ne devriez jamais faire de visibilité à travers les couches. Vous devriez être capable de changer les entités sauvegardées sans affecter la couche client. Avec RIA c'est très dur. Je pense que OData va dans la même catégorie que WCF RIA.

Si vous avez besoin de créer des requêtes côté client, vous utilisez un modèle de spécification - n'utilisez pas iqueryable - vous serez alors indépendant des entités backend.

Bonne chance.
twitter: @lroal