Nous avons donc vendu au client un logiciel B2B.
Lors de la présentation, il a tout aimé, mais lors de la mise en œuvre, il s'est avéré que quelque chose ne convenait toujours pas. Vous pouvez bien sûr dire que vous devez suivre les «meilleures pratiques» et vous transformer en produit, et non l'inverse. Cela peut fonctionner si vous avez une marque forte (par exemple, trois grandes lettres et que vous pouvez envoyer les trois petites lettres). Sinon, ils vous expliqueront rapidement que le client a tout accompli grâce à ses processus commerciaux uniques, et améliorons mieux votre produit ou cela ne fonctionnera pas. Il y a une option pour refuser et se référer au fait que des licences ont déjà été achetées, et il n'y a nulle part où aller depuis le sous-marin. Mais sur des marchés relativement étroits, une telle stratégie ne fonctionnera pas longtemps.
Nous devons modifier.
Les approches
Il existe plusieurs approches de base pour l'adaptation des produits.
Monolith
Toutes les modifications sont apportées directement au code source du produit, mais sont incluses dans certaines options. En règle générale, dans ces produits, il existe des formulaires monstrueux avec des paramètres qui, pour ne pas se tromper, se voient attribuer leurs numéros ou codes. L'inconvénient de cette approche est que le code source se transforme en gros spaghetti, dans lesquels il existe tellement de cas d'utilisation différents qu'il devient très long et coûteux à maintenir. Chaque option suivante nécessite de plus en plus de ressources. Les performances d'un tel produit laissent également beaucoup à désirer. Et si la langue dans laquelle le produit est écrit ne prend pas en charge les pratiques modernes comme l'héritage et le polymorphisme, alors tout devient très triste.
Copier
Le client reçoit l'intégralité du code source du produit avec une licence pour le modifier. Souvent, ces fournisseurs disent au client qu'ils n'adapteront pas le produit eux-mêmes, car il sera trop cher (il est beaucoup plus rentable pour un fournisseur de vendre des licences que de contacter des services). Mais ils ont des sous-traitants familiers qui embauchent des employés relativement bon marché et de haute qualité quelque part dans des pays tiers qui sont prêts à les aider. Il existe également des situations où des améliorations seront effectuées directement par les spécialistes du client (s’ils disposent d’unités de dotation). Dans de tels cas, le code source est pris comme point de départ et le code modifié n'aura aucun lien avec ce qui était à l'origine et vivra sa propre vie. Dans ce cas, vous pouvez retirer en toute sécurité au moins la moitié du produit d'origine et le remplacer par votre propre logique.
Fusion
Il s'agit d'un mélange des deux premières approches. Mais là-dedans, le développeur qui corrige le code doit toujours se rappeler: «la fusion arrive». Lorsqu'une nouvelle version du produit source est publiée, elle devra dans la plupart des cas fusionner manuellement les modifications dans le code source et le code modifié. Le problème est que dans tout conflit, il sera nécessaire de se rappeler pourquoi certains changements ont été apportés, et cela pourrait être il y a très longtemps. Et si le refactoring de code a été effectué dans le produit d'origine (par exemple, les blocs de code ont été simplement réarrangés), la fusion sera très laborieuse.
Modularité
Logiquement l'approche la plus correcte. Dans ce cas, le code source du produit ne subit aucune modification et des modules supplémentaires sont ajoutés pour étendre la fonctionnalité. Cependant, pour mettre en œuvre un tel schéma, le produit doit avoir une architecture qui lui permette d'être étendu de cette manière.
La description
Plus loin, avec des exemples, je montrerai comment nous élargissons les produits développés sur la base de la plate-forme
lsFusion ouverte et gratuite.
Un élément clé du système est le module. Un module est un fichier texte avec l'extension
lsf , qui contient le
code lsFusion . Dans chaque module, la logique du domaine (fonctions, classes, actions) et la logique de présentation (formulaires, navigateur) sont déclarées. Les modules sont généralement situés dans des répertoires, divisés par un principe logique. Un produit est une collection de modules qui implémentent ses fonctionnalités et stockés dans un référentiel séparé.
Les modules sont interdépendants. Un module dépend d'un autre s'il utilise sa logique (par exemple, fait référence à des propriétés ou à des formulaires).
Lorsqu'un nouveau client apparaît, un référentiel séparé (Git ou Subversion) est lancé pour lui, dans lequel des modules avec les modifications nécessaires seront créés. Ce référentiel définit le module dit supérieur. Au démarrage du serveur, seuls les modules seront connectés, dont il dépend directement ou transitivement à travers d'autres modules. Cela permet au client d'utiliser non pas toutes les fonctionnalités du produit, mais uniquement la pièce dont il a besoin.
Jenkins crée une tâche qui combine les modules produit et client dans un seul fichier jar, qui est ensuite installé sur un serveur de production ou de test.
Considérez plusieurs cas principaux d'améliorations qui surviennent dans la pratique:
Supposons que nous ayons un module de
commande dans le produit, qui décrit la logique de commande standard:
Le client
X souhaite ajouter un pourcentage de remise et un prix de remise pour la ligne de commande.
Tout d'abord, un nouveau module
OrderX est créé dans le référentiel client. Dans son en-tête, une dépendance est placée sur le module
Order d' origine:
Dans ce module, nous déclarons de nouvelles propriétés sous lesquelles des champs supplémentaires seront créés dans les tables et les ajouterons au formulaire:
Nous rendons le prix réduit indisponible pour l'enregistrement. Il sera calculé comme un événement distinct lorsque le prix initial ou le pourcentage de remise change:
Maintenant, vous devez modifier le calcul du montant sur la ligne de commande (il doit prendre en compte notre nouveau prix discount). Pour ce faire, nous créons généralement certains «points d'entrée» où d'autres modules peuvent insérer leur comportement. Au lieu de la déclaration initiale de la propriété sum dans le module Order, nous utilisons ce qui suit:
Dans ce cas, la valeur de la propriété
sum sera collectée dans un CAS, où WHEN peut être dispersé sur différents modules. Il est garanti que si le module A dépend du module B, alors tout QUAND du module B fonctionnera plus tard que QUAND du module A. Pour calculer correctement le
montant actualisé, la déclaration suivante est ajoutée au module
OrderX :
En conséquence, si une remise est fixée, le montant en sera soumis, sinon l'expression originale.
Supposons qu'un client souhaite ajouter une restriction selon laquelle le montant de la commande ne doit pas dépasser un certain montant spécifié. Dans le même module
OrderX ,
nous déclarons une propriété dans laquelle la valeur limite sera stockée et l'ajoutons au formulaire d'
options standard (vous pouvez créer un formulaire séparé avec des paramètres si vous le souhaitez):
Ensuite, dans le même module, nous déclarons le montant de la commande, le montrons sur le formulaire et ajoutons une limite à son excédent:
Et enfin, le client a demandé de modifier légèrement la conception du formulaire d'édition de commande: pour faire l'en-tête de commande à gauche des lignes avec un séparateur, ainsi que pour toujours afficher les prix avec une précision de deux caractères. Pour ce faire, le code suivant est ajouté à son module, ce qui modifie la conception générée standard du bon de commande:
En conséquence, nous obtenons deux modules
Order (dans le produit), dans lesquels la logique de base de la commande est implémentée, et
OrderX (chez le client), dans lequel la logique de remise nécessaire est implémentée:
Il convient de noter que le module
OrderX peut être appelé
OrderDiscount et transféré directement au produit. Ensuite, si nécessaire, il sera possible pour chaque client de connecter facilement la fonctionnalité avec des remises.
C'est loin de toutes les possibilités qu'offre la plateforme pour étendre les fonctionnalités des modules individuels. Par exemple, en utilisant l'héritage, vous pouvez implémenter de manière modulaire la logique de
registre .
S'il y a des changements dans le code source du produit qui contredisent le code dans le module dépendant, une erreur sera générée au démarrage du serveur. Par exemple, si le bon de
commande est
supprimé dans le module
Commande , au démarrage, il y aura une erreur indiquant que le bon de
commande est introuvable dans le module
OrderX . De plus, l'erreur sera mise en évidence dans l'
EDI . De plus, l'EDI a une fonction pour rechercher toutes les erreurs dans le projet, ce qui vous permet d'identifier tous les problèmes survenus en raison de la mise à jour de la version du produit.
En pratique, nous avons tous les référentiels (du produit et de tous les clients) connectés au même projet, nous refactorisons donc calmement le produit, tout en changeant la logique dans les modules clients où il est utilisé.
Conclusion
Une telle architecture micromodulaire offre les avantages suivants:
- Chaque client est connecté uniquement à la fonctionnalité dont il a besoin . La structure de sa base de données ne contient que les champs qu'il utilise. L'interface de la solution finale ne contient pas d'éléments inutiles. Le serveur et le client n'effectuent pas d'événements et de vérifications inutiles.
- Flexibilité dans les modifications apportées aux fonctionnalités de base . Directement dans le projet du client, vous pouvez apporter des modifications à absolument n'importe quelle forme du produit, ajouter des événements, de nouveaux objets et propriétés, des actions, changer la conception et bien plus encore.
- La livraison des nouvelles améliorations requises par le client est considérablement accélérée . Avec chaque demande de modification, vous n'avez pas besoin de réfléchir à la manière dont cela affectera les autres clients. Pour cette raison, de nombreuses améliorations peuvent être apportées et mises en service dès que possible (souvent en quelques heures).
- Un schéma plus pratique pour étendre les fonctionnalités du produit . Tout d'abord, toute fonctionnalité peut être incluse pour un client spécifique qui est prêt à l'essayer, puis, en cas de mise en œuvre réussie, les modules sont entièrement transférés vers le référentiel de produits.
- Indépendance de la base de code . Étant donné que de nombreuses améliorations sont fournies dans le cadre de contrats de service client, formellement, l'intégralité du code développé dans le cadre de ces contrats appartient au client. Avec ce schéma, une séparation complète du code produit appartenant au vendeur du code appartenant au client est assurée. Sur demande, nous transférons le référentiel sur le serveur du client, où il peut modifier les fonctionnalités dont il a besoin avec ses propres développeurs. De plus, si le fournisseur concède des licences pour des modules de produits individuels, le client ne dispose pas du code source des modules pour lesquels il n'y a pas de licence. Ainsi, il n'a pas la capacité technique de les connecter indépendamment en violation des conditions de licence.
Le schéma de modularité décrit ci-dessus à l'aide d'extensions dans la programmation est le plus souvent appelé
mix in . Par exemple, Microsoft Dynamics a récemment introduit le concept d'extension, qui vous permet également d'étendre les modules de base. Cependant, une programmation de niveau beaucoup plus bas est requise là-bas, ce qui nécessite à son tour des qualifications plus élevées des développeurs. De plus, contrairement à lsFusion, l'expansion des événements et des restrictions nécessite les «points d'entrée» initiaux du produit pour en tirer parti.
À l'heure actuelle, selon le schéma décrit ci-dessus, nous soutenons et mettons en œuvre un
système ERP pour la vente au détail avec plus de 30 clients relativement importants, qui se compose de plus de 1000 modules. Parmi les clients, il y a les réseaux FMCG, ainsi que les pharmacies, les magasins de vêtements, les chaînes de magasins de drogerie, les grossistes et autres. Dans le produit, respectivement, il existe des catégories distinctes de modules qui sont connectés en fonction de l'industrie et des processus métier utilisés.