Dans cet article, je veux parler de certains langages et technologies populaires qui incluent des Ă©lĂ©ments de programmation dĂ©clarative - PL / SQL , MS LINQ et GraphQL . J'essaierai de comprendre quelles tĂąches y sont rĂ©solues Ă l'aide de la programmation dĂ©clarative, Ă quel point les approches dĂ©clarative et impĂ©rative sont Ă©troitement liĂ©es, quels avantages cela donne et quelles idĂ©es peuvent en ĂȘtre tirĂ©es.
Extensions procédurales SQL
Commençons par un domaine dans lequel cette association est depuis longtemps devenue un standard de l'industrie: les langages d'accĂšs aux donnĂ©es. Le plus cĂ©lĂšbre d'entre eux est PL / SQL, une extension procĂ©durale du langage SQL. Ce langage vous permet de traiter des donnĂ©es dans une base de donnĂ©es relationnelle en utilisant Ă la fois des styles de programmation impĂ©ratifs (variables, instructions de contrĂŽle, fonctions, objets) et dĂ©claratifs (expressions SQL). Ă l'aide d'une requĂȘte SQL, nous pouvons dĂ©crire les propriĂ©tĂ©s des donnĂ©es dont nous avons besoin - quels champs sont nĂ©cessaires, Ă partir de quelles tables les extraire, comment elles sont liĂ©es les unes aux autres, Ă quelles contraintes elles doivent se conformer, comment elles doivent ĂȘtre agrĂ©gĂ©es, etc. Et le serveur de base de donnĂ©es Ă©tablira indĂ©pendamment un plan d'exĂ©cution des requĂȘtes et trouvera tous les ensembles possibles de champs qui remplissent les conditions spĂ©cifiĂ©es. La partie procĂ©durale de PL / SQL vous permet de mettre en Ćuvre ces tĂąchesqui sont difficiles ou impossibles Ă exprimer sous une forme dĂ©clarative - pour traiter le rĂ©sultat d'une requĂȘte dans une boucle, effectuer des calculs arbitraires, structurer le code en fonctions et en classes.
Les composants procĂ©duraux et dĂ©claratifs du langage sont Ă©troitement intĂ©grĂ©s. PL / SQL vous permet de dĂ©clarer des fonctions, d'exĂ©cuter des requĂȘtes Ă l'intĂ©rieur et de renvoyer leurs rĂ©sultats, d'utiliser des fonctions Ă l'intĂ©rieur d'une requĂȘte, en leur transmettant les valeurs des champs de la table comme arguments. Vous pouvez accĂ©der aux rĂ©sultats d'une requĂȘte Ă l'aide de curseurs puis boucler impĂ©rativement tous les enregistrements reçus. Les curseurs vous donnent plus de contrĂŽle sur le contenu des tables et vous permettent d'implĂ©menter une logique de traitement de donnĂ©es beaucoup plus complexe que d'utiliser SQL seul. Un curseur peut ĂȘtre affectĂ© Ă une variable de curseur et passĂ© en argument Ă des fonctions, des procĂ©dures ou mĂȘme une application cliente. Le code de requĂȘte lui-mĂȘme peut ĂȘtre gĂ©nĂ©rĂ© dynamiquement par une sĂ©quence de commandes impĂ©ratives.La combinaison de procĂ©dures et de requĂȘtes, Ă l'aide de quelques ajustements, vous permet d'implĂ©menter des requĂȘtes rĂ©cursives. Il existe mĂȘme des fonctionnalitĂ©s orientĂ©es objet dans PL / SQL qui vous permettent de dĂ©clarer des types de donnĂ©es composites pour les champs de table, d'y inclure des mĂ©thodes et de crĂ©er des classes par hĂ©ritage.
PL / SQL vous permet d'implĂ©menter la logique mĂ©tier cĂŽtĂ© serveur de base de donnĂ©es. De plus, l'implĂ©mentation du modĂšle de domaine sera assez proche de sa description. Les concepts de base du modĂšle de domaine seront mappĂ©s sur le modĂšle de donnĂ©es relationnelles. Les concepts correspondront aux tables, aux attributs - Ă leurs champs. Les contraintes sur les valeurs de champ peuvent ĂȘtre intĂ©grĂ©es dans les descriptions de table. Et les relations avec d'autres tables peuvent ĂȘtre dĂ©finies Ă l'aide de clĂ©s Ă©trangĂšres. Les concepts abstraits construits sur la base des concepts de base correspondront Ă la vue. Ils peuvent ĂȘtre utilisĂ©s dans des requĂȘtes avec des tables, y compris pour crĂ©er d'autres vues. Les vues sont construites sur la base de requĂȘtes, ce qui vous permet d'utiliser toute la puissance et la flexibilitĂ© de SQL. De cette façon,Ă partir de tables et de vues, vous pouvez crĂ©er un modĂšle de domaine assez complexe et Ă plusieurs niveaux complĂštement dans un style dĂ©claratif. Et tout ce qui ne rentre pas bien dans le style dĂ©claratif peut ĂȘtre implĂ©mentĂ© Ă l'aide de procĂ©dures et de fonctions.
Le principal problÚme est que le code PL / SQL est exécuté exclusivement cÎté serveur de base de données. Cela rend difficile la mise à l'échelle d'une telle solution. De plus, le modÚle résultant sera lié de maniÚre rigide à la base de données relationnelle et il sera problématique d'y inclure des données provenant d'autres sources.
RequĂȘte intĂ©grĂ©e au langage
La requĂȘte intĂ©grĂ©e au langage (LINQ) est un composant populaire de la plate-forme .NET qui vous permet d'inclure naturellement des expressions de requĂȘte SQL dans votre code de langage orientĂ© objet principal. Contrairement Ă PL / SQL, qui ajoute un paradigme impĂ©ratif Ă SQL cĂŽtĂ© serveur de base de donnĂ©es, LINQ amĂšne SQL au niveau de l'application. GrĂące Ă cela, les requĂȘtes dans LINQ peuvent ĂȘtre utilisĂ©es pour obtenir des donnĂ©es non seulement Ă partir de bases de donnĂ©es relationnelles, mais Ă©galement Ă partir de collections d'objets, de documents XML et d'autres requĂȘtes LINQ.
L'architecture LINQ est assez flexible et les dĂ©finitions de requĂȘte sont profondĂ©ment intĂ©grĂ©es au modĂšle POO. LINQ vous permet de crĂ©er vos propres fournisseurs pour accĂ©der Ă de nouvelles sources de donnĂ©es. Vous pouvez Ă©galement dĂ©finir votre propre façon d'exĂ©cuter la requĂȘte et, par exemple, convertir l'arborescence d'expression LINQ de la requĂȘte en requĂȘte vers la source de donnĂ©es souhaitĂ©e. Vous pouvez utiliser des expressions et fonctions lambda dĂ©finies dans le code de votre application dans le corps de votre requĂȘte. Certes, dans le cas de LINQ to SQL, la requĂȘte sera exĂ©cutĂ©e cĂŽtĂ© serveur de base de donnĂ©es, oĂč ces fonctions ne seront pas disponibles, mais des procĂ©dures stockĂ©es peuvent ĂȘtre utilisĂ©es Ă la place. La requĂȘte est l'essence mĂȘme du langage de premier niveau, vous pouvez travailler avec elle comme avec un objet ordinaire. Le compilateur est capable de dĂ©duire automatiquement le type du rĂ©sultat de la requĂȘte et de gĂ©nĂ©rer la classe appropriĂ©e, mĂȘme si elle n'a pas Ă©tĂ© explicitement dĂ©clarĂ©e.
Essayons d'utiliser LINQ pour crĂ©er un modĂšle de domaine sous la forme d'un ensemble de requĂȘtes. Les faits initiaux peuvent ĂȘtre placĂ©s dans des listes cĂŽtĂ© application ou dans des tables cĂŽtĂ© base de donnĂ©es, et les concepts abstraits peuvent ĂȘtre formatĂ©s sous forme de requĂȘtes LINQ. LINQ vous permet de crĂ©er des requĂȘtes basĂ©es sur d'autres requĂȘtes en les spĂ©cifiant dans la clause FROM. Cela vous permet de construire un nouveau concept basĂ© sur des concepts existants. Les champs de la section SELECT correspondront aux attributs du concept. Et la section WHERE contiendra les dĂ©pendances entre les concepts. Un exemple avec des factures d'une publication prĂ©cĂ©dente ressemblera Ă ceci.
Nous placerons des objets avec des comptes et des informations client dans les listes:
List<Bill> bills = new List<Bill>() { ... };
List<Client> clients = new List<Client>() { ... };
Et puis crĂ©ons des requĂȘtes pour qu'ils obtiennent des factures impayĂ©es et des dĂ©biteurs:
IEnumerable<Bill> unpaidBillsQuery =
from bill in bills
where bill.AmountToPay > bill.AmountPaid
select bill;
IEnumerable<Client> debtorsQuery =
from bill in unpaidBillsQuery
join client in clients on bill.ClientId equals client.ClientId
select client;
Le modĂšle de domaine implĂ©mentĂ© Ă l'aide de LINQ a pris une forme plutĂŽt bizarre - plusieurs styles de programmation sont impliquĂ©s Ă la fois. Le niveau supĂ©rieur du modĂšle a une sĂ©mantique impĂ©rative. Il peut ĂȘtre reprĂ©sentĂ© comme des chaĂźnes de transformations d'objets, crĂ©ant des collections d'objets au-dessus des collections. Les objets de requĂȘte sont des Ă©lĂ©ments du monde POO. Ils doivent ĂȘtre crĂ©Ă©s, affectĂ©s Ă des variables et les rĂ©fĂ©rences Ă celles-ci doivent ĂȘtre transmises Ă d'autres requĂȘtes. Au niveau intermĂ©diaire, l'objet de requĂȘte implĂ©mente la procĂ©dure d'exĂ©cution d'une requĂȘte, qui est fonctionnellement personnalisĂ©e avec des expressions lambda qui vous permettent de former la structure de rĂ©sultat dans la section SELECT et de filtrer les enregistrements dans la clause WHERE. Le niveau interne est reprĂ©sentĂ© par une procĂ©dure d'exĂ©cution de requĂȘte qui a une sĂ©mantique logique et est basĂ©e sur l'algĂšbre relationnelle.
Bien que LINQ ait permis de dĂ©crire le modĂšle de domaine, la syntaxe SQL vise principalement Ă rĂ©cupĂ©rer et Ă manipuler des donnĂ©es. Il manque quelques constructions qui seraient utiles dans la modĂ©lisation. Si en PL / SQL la structure des concepts de base Ă©tait trĂšs clairement reprĂ©sentĂ©e sous forme de tables et de vues, alors dans LINQ, elle s'est avĂ©rĂ©e ĂȘtre rendue en code POO. En outre, alors que les tables et les vues peuvent ĂȘtre rĂ©fĂ©rencĂ©es par leur nom, les requĂȘtes LINQ peuvent ĂȘtre rĂ©fĂ©rencĂ©es dans un style impĂ©ratif. De plus, SQL est limitĂ© par le modĂšle relationnel et a des capacitĂ©s limitĂ©es lors de l'utilisation de structures sous forme de graphes ou d'arbres.
ParallĂšles entre le modĂšle relationnel et la programmation logique
Vous pouvez voir que les implĂ©mentations SQL et Prolog du modĂšle prĂ©sentent des similitudes. En SQL, nous construisons une vue basĂ©e sur des tables ou d'autres vues, et dans Prolog, nous construisons des rĂšgles basĂ©es sur des faits et des rĂšgles. En SQL, les tables sont une collection de champs et les prĂ©dicats dans Prolog sont une collection d'attributs. En SQL, nous spĂ©cifions les dĂ©pendances entre les champs de table sous forme d'expressions dans la clause WHERE et dans Prolog, en utilisant des prĂ©dicats et des variables boolĂ©ennes qui lient les attributs de prĂ©dicat les uns aux autres. Dans les deux cas, nous dĂ©finissons de maniĂšre dĂ©clarative la spĂ©cification de la solution, et le moteur d'exĂ©cution de requĂȘte intĂ©grĂ© nous renvoie les enregistrements trouvĂ©s dans SQL ou les valeurs possibles des variables dans Prolog.
Cette similitude n'est pas accidentelle. Bien que le fondement théorique de SQL - algÚbre relationnelle ait été développé en parallÚle avec la programmation logique, mais plus tard, un lien théorique a été révélé entre eux. Ils ont une base mathématique commune - la logique du premier ordre. Le modÚle de données relationnel décrit les rÚgles de création de relations entre les tables de données, la programmation logique - entre les instructions. Les deux théories utilisent des termes différents, sont appliquées dans des domaines différents, ont été développées en parallÚle, mais elles avaient une base mathématique commune.
Ă proprement parler, le calcul relationnel est une adaptation de la logique du premier ordre pour travailler avec des donnĂ©es tabulaires. Cette question est discutĂ©e plus en dĂ©tail ici.... Autrement dit, toute expression d'algĂšbre relationnelle (toute requĂȘte SQL) peut ĂȘtre reformulĂ©e en une expression de logique du premier ordre, puis implĂ©mentĂ©e dans Prolog. Mais pas l'inverse. Le calcul relationnel est un sous-ensemble de la logique du premier ordre. Cela signifie que pour certains types d'Ă©noncĂ©s admissibles en logique du premier ordre, nous ne pouvons pas trouver d'analogies en algĂšbre relationnelle. Par exemple, les capacitĂ©s des requĂȘtes rĂ©cursives en SQL sont trĂšs limitĂ©es et la construction de relations transitives n'est pas toujours disponible. Les opĂ©rations Prolog telles que la disjonction de cible et la nĂ©gation comme le refus sont beaucoup plus difficiles Ă implĂ©menter en SQL. La syntaxe flexible de Prolog vous offre plus de flexibilitĂ© pour travailler avec des structures imbriquĂ©es complexes et prend en charge les opĂ©rations de correspondance de modĂšles sur celles-ci.Cela le rend pratique lorsque vous travaillez avec des structures de donnĂ©es complexes telles que des arbres et des graphiques.
Mais il faut tout payer. Les algorithmes d'exĂ©cution de requĂȘtes intĂ©grĂ©s dans les bases de donnĂ©es relationnelles sont plus simples et moins polyvalents que les algorithmes d'infĂ©rence de Prolog. Cela permet de les optimiser et d'atteindre des performances bien supĂ©rieures. Prolog est Ă©galement incapable de traiter rapidement des millions de lignes dans des bases de donnĂ©es relationnelles. De plus, l'algorithme d'infĂ©rence de Prolog ne garantit pas du tout la fin de l'exĂ©cution du programme - la sortie de certaines instructions peut conduire Ă une rĂ©cursion infinie.
Ă propos, Ă l'intersection des bases de donnĂ©es et de la programmation logique, il existe Ă©galement des technologies telles que les bases de donnĂ©es dĂ©ductives et le langage des rĂšgles et des requĂȘtes pour eux Datalog. Au lieu d'enregistrements dans des tables, les bases de donnĂ©es dĂ©ductives stockent de grandes quantitĂ©s de faits et de rĂšgles dans un style logique. Et Datalog ressemble Ă Prolog, mais il se concentre sur le travail avec des faits combinĂ©s dans des ensembles, pas des faits uniques. De plus, certaines fonctionnalitĂ©s de la logique du premier ordre ont Ă©tĂ© supprimĂ©es afin d'optimiser l'algorithme d'infĂ©rence pour un travail rapide avec de grandes quantitĂ©s de donnĂ©es. La syntaxe moins expressive d'un langage logique a donc aussi ses avantages.
Approche déclarative de la description de la couche API
SQL lie la construction du modĂšle Ă la couche d'accĂšs aux donnĂ©es. Mais la programmation dĂ©clarative se dĂ©veloppe activement Ă l'extrĂ©mitĂ© opposĂ©e de l'application - dans la couche API. Sa particularitĂ© est que les informations sur la structure des requĂȘtes doivent ĂȘtre disponibles pour ceux qui utilisent cette API. Avoir une description formelle de la structure des demandes et des rĂ©ponses est une bonne forme. En consĂ©quence, il existe un dĂ©sir de synchroniser cette description avec le code d'application, par exemple, gĂ©nĂ©rer des classes de demande et de rĂ©ponse sur la base de celui-ci. Dans lequel vous devrez ensuite Ă©crire la logique de traitement des requĂȘtes.
GraphQL est un framework de crĂ©ation d'API qui va bien au-delĂ de cette approche traditionnelle et offre non seulement un langage de requĂȘte, mais Ă©galement un environnement d'exĂ©cution de requĂȘte. Il n'est pas nĂ©cessaire de gĂ©nĂ©rer du code, le moteur d'exĂ©cution comprend de toute façon les descriptions des requĂȘtes. Pour implĂ©menter l'API Ă l'aide de GraphQL, vous avez besoin:
- décrire les types de données (objets) de l'application qui font partie des demandes et des réponses;
- décrire la structure des demandes et des réponses;
- implémenter des fonctions qui implémentent la logique de création d'objets pour obtenir les valeurs de leurs champs.
Les types de donnĂ©es sont des descriptions de champs d'objet. Les types tels que les types scalaires, les listes, les Ă©numĂ©rations et les rĂ©fĂ©rences aux types imbriquĂ©s sont pris en charge. Ătant donnĂ© que les champs de type peuvent contenir des rĂ©fĂ©rences Ă d'autres types, l'ensemble du schĂ©ma de donnĂ©es peut ĂȘtre reprĂ©sentĂ© sous forme de graphique. La demande est une description de la structure de donnĂ©es demandĂ©e Ă l'API. La description de la demande comprend une liste des objets requis, leurs champs et les attributs d'entrĂ©e. Chaque type de donnĂ©es et chacun de ses champs doivent ĂȘtre associĂ©s Ă une fonction de rĂ©solution. Le rĂ©solveur de type (objet) dĂ©crit l'algorithme pour obtenir ses objets, le rĂ©solveur de champ dĂ©crit les valeurs du champ objet. Ils reprĂ©sentent des fonctions dans l'un des langages fonctionnels ou orientĂ©s objet. Le runtime GraphQL reçoit une requĂȘte, dĂ©termine les types de donnĂ©es requis, appelle leurs rĂ©solveurs, y compris le long d'une chaĂźne d'objets imbriquĂ©s,recueille un objet de rĂ©ponse.
GraphQL combine la description de schĂ©ma de donnĂ©es dĂ©clarative avec des algorithmes impĂ©ratifs ou fonctionnels pour les obtenir. Le schĂ©ma de donnĂ©es est dĂ©crit explicitement et est au cĆur de l'application. De nombreuses personnes soulignent qu'il est recommandĂ© de crĂ©er un schĂ©ma de donnĂ©es qui ne duplique pas les schĂ©mas de source de donnĂ©es, mais qui soit conforme au modĂšle de domaine. Cela fait de GraphQL une solution trĂšs populaire pour l'intĂ©gration de sources de donnĂ©es disparates.
Ainsi, le langage GraphQL permet d'exprimer le modĂšle de domaine de maniĂšre assez claire, de le distinguer du reste du code, de rapprocher le modĂšle et son implĂ©mentation. Malheureusement, le composant dĂ©claratif du langage se limite uniquement Ă la description de la composition des types de donnĂ©es; toutes les autres relations entre les Ă©lĂ©ments du modĂšle doivent ĂȘtre implĂ©mentĂ©es Ă l'aide de rĂ©solveurs. D'une part, les rĂ©solveurs permettent Ă un dĂ©veloppeur de mettre en Ćuvre indĂ©pendamment toute mĂ©thode d'obtention de donnĂ©es pour un objet et toute relation entre eux. Mais, d'un autre cĂŽtĂ©, vous devrez essayer d'implĂ©menter des options de requĂȘte plus complexes que, par exemple, l'accĂšs Ă un enregistrement par clĂ©. D'une part, le schĂ©ma de donnĂ©es dans GraphQL montre clairement la relation entre la couche API et la couche d'accĂšs aux donnĂ©es. Mais, d'un autre cĂŽtĂ©, la couche principale Ă laquelle le schĂ©ma de donnĂ©es est liĂ© est la couche API.Le contenu du schĂ©ma de donnĂ©es s'y ajuste; il ne contiendra pas d'entitĂ©s qui ne sont pas impliquĂ©es dans le traitement des demandes. Bien que la puissance expressive du langage de description de donnĂ©es GraphQL soit infĂ©rieure Ă des langages dĂ©claratifs Ă part entiĂšre comme SQL et Prolog, la popularitĂ© de ce framework montre que les outils de description dĂ©clarative du modĂšle peuvent et doivent faire partie des langages de programmation modernes.
Je vais résumer
PL / SQL est un langage pratique Ă la fois pour dĂ©crire un modĂšle de domaine sous forme de tables et de vues, et pour la logique de son utilisation. Les composantes dĂ©clarative et procĂ©durale sont Ă©troitement intĂ©grĂ©es et complĂ©mentaires. Le principal problĂšme est que ce langage est Ă©troitement liĂ© Ă l'emplacement de stockage des donnĂ©es, il ne peut ĂȘtre exĂ©cutĂ© que du cĂŽtĂ© serveur de base de donnĂ©es et la logique d'exĂ©cution de la requĂȘte est limitĂ©e au modĂšle de donnĂ©es relationnel.
CĂŽtĂ© application, vous pouvez utiliser des technologies telles que LINQ et GraphQL pour dĂ©crire le modĂšle sous une forme dĂ©clarative. En utilisant le schĂ©ma de donnĂ©es GraphQL, vous pouvez dĂ©crire clairement et trĂšs clairement la structure du modĂšle de domaine, l'imbrication de ses concepts. Et le runtime est capable de collecter automatiquement les objets requis. Malheureusement, toutes les autres relations et connexions entre les concepts, Ă l'exception de leur imbrication, doivent ĂȘtre implĂ©mentĂ©es dans la couche des fonctions de rĂ©solution. LINQ a des avantages et des inconvĂ©nients opposĂ©s. La syntaxe SQL flexible vous donne plus de flexibilitĂ© pour dĂ©crire les relations entre les concepts. Mais en dehors de la requĂȘte, la dĂ©clarativitĂ© prend fin, les objets de requĂȘte sont des Ă©lĂ©ments du monde OOP. Ils doivent ĂȘtre crĂ©Ă©s, affectĂ©s Ă des variables et utilisĂ©s dans un style impĂ©ratif.
Je voudrais combiner les avantages de LINQ et GraphQL. Pour que la description de la structure des concepts soit claire comme dans GraphQL, et que les relations entre eux puissent ĂȘtre dĂ©finies en fonction de la logique comme en SQL. Et pour que les dĂ©finitions de concepts soient disponibles directement par nom en tant que classes, sans qu'il soit nĂ©cessaire de crĂ©er explicitement leurs objets, de les affecter Ă des variables, de leur passer des rĂ©fĂ©rences, etc.
Je vais commencer à concevoir une telle solution en développant un langage pour décrire un modÚle de domaine. Mais pour cela, il est nécessaire de faire un tour d'horizon des langages existants de représentation des connaissances. Par conséquent, dans la prochaine publication, je veux parler de programmation logique, RDF, OWL et langages de logique de trame, les comparer et essayer de trouver de telles fonctionnalités qui seraient intéressantes pour le langage conçu pour décrire la logique métier.
Pour ceux qui ne veulent pas attendre la sortie de toutes les publications sur Habré, il existe un texte intégral de style scientifique en anglais, disponible sur le lien: Programmation orientée ontologie hybride pour le traitement de données semi-structurées .
Liens vers les publications précédentes:
Conception d'un langage de programmation multi-paradigme. Partie 1 - à quoi ça sert?