Points Clés
- Le système de typage flexible de Ballerina apporte le meilleur des langages typés statiquement et dynamiquement en termes de sécurité, de clarté et de rapidité de développement.
- Ballerina traite les données comme des citoyens de premier ordre qui peuvent être créés sans cérémonie supplémentaire, tout comme les chaînes et les nombres.
- Ballerina propose un langage de requête riche qui permet aux développeurs d'exprimer avec éloquence la logique métier et la manipulation des données.
- Dans les records de Ballerina, les champs peuvent être obligatoires ou facultatifs.
- Ballerina prend en charge JSON out of the box.
Dans les systèmes d'information que j'ai construits au cours de la dernière décennie, les données sont échangées entre des programmes tels que des applications frontend, des serveurs backend et des services worker. Ces programmes utilisent des formats d'échange, comme JSON, pour communiquer via le réseau.
Au fil des années, j'ai remarqué que la complexité d'un programme ne dépendait pas seulement de la complexité des besoins de l'entreprise, mais également de l'approche que j'adoptais pour représenter les données dans mes programmes.
Dans les langages à typage statique (comme Java, C#, Go, OCaml ou Haskell), il semble naturel de représenter les données avec des types ou des classes personnalisés, tandis que dans les langages à typage dynamique (comme JavaScript, Ruby, Python ou Clojure), nous utilisent généralement des structures de données génériques, comme des maps et des tableaux.
Chaque approche a ses avantages et ses coûts. Lorsque nous représentons des données avec des types statiques, nous bénéficions d'un excellent support de notre IDE et de la sécurité de notre système de types, mais cela rend le code plus verbeux et le modèle de données rigide.
D'autre part, dans les langages à typage dynamique, nous représentons les données avec des maps flexibles. Cela nous permet de créer rapidement du code de petite à moyenne taille sans aucun type de cérémonie, mais nous opérons dans la nature. Notre IDE ne nous aide pas à compléter automatiquement les noms de champs, et lorsque nous tapons mal les noms de champs, nous obtenons des erreurs d'exécution.
L'approche rafraîchissante de Ballerina envers les types
Jusqu'à ce que je découvre Ballerina, je pensais que ce compromis faisait partie intégrante de la programmation avec laquelle nous étions obligés de vivre. Mais j'avais tort : il est possible de combiner le meilleur des deux mondes. Il est possible de se déplacer rapidement sans compromettre la sécurité et la clarté. Il est possible de bénéficier d'un système de type flexible.
Je ne peux pas me permettre de marcher, c'est trop lent.
J'ai peur de courir, c'est trop risqué.
Je veux circuler avec aisance et confiance. Comme une ballerine.
Les données en tant que citoyen de premier ordre
Lorsque nous écrivons un programme qui manipule des données, il est préférable de traiter les données comme un citoyen de premier ordre. L'un des privilèges des citoyens de premier ordre est qu'ils peuvent être créés sans cérémonie supplémentaire, tout comme les nombres et les chaînes.
Malheureusement, dans les langages à typage statique, les données n'ont généralement pas le privilège d'être créées sans cérémonie. Vous devez utiliser un constructeur nommé pour créer des données. Lorsque les données ne sont pas imbriquées, l'absence de littéraux de données n'est pas trop gênante, par exemple lors de la création d'un membre de bibliothèque nommé Kelly Kapowski âgé de 17 ans.
Member kelly = new Member(
"Kelly",
"Kapowski",
17
);
Mais avec des données imbriquées, l'utilisation d'un constructeur nommé devient verbeuse. Voici à quoi ressemble la création de données lorsque nous incluons la liste des livres que Kelly détient actuellement, en supposant un modèle de données de bibliothèque simpliste, où un livre n'a qu'un titre et un auteur.
Member kelly = new Member(
"Kelly",
"Kapowski",
17,
List.of(
new Book(
"The Volleyball Handbook",
new Author("Bob", "Miller")
)
)
);
Dans les langages à typage dynamique, comme JavaScript, l'utilisation de littéraux de données rend beaucoup plus naturelle la création de données imbriquées.
var kelly = {
firstName: "Kelly",
lastName: "Kapowski",
age: 17,
books: [
{
title: "The Volleyball Handbook",
author: {
firstName: "Bob",
lastName: "Miller"
}
}
]
};
Le problème avec l'approche des données des langages à typage dynamique est que les données sont sauvages. La seule chose que vous savez sur vos données, c'est qu'il s'agit d'une map imbriquée. Par conséquent, vous devez vous fier à la documentation pour savoir quel type de données vous avez en main.
La première chose que j'ai appréciée dans Ballerina est qu'il m'a donné la possibilité de créer mes types personnalisés tout en gardant la commodité de créer des données via des littéraux de données.
Avec Ballerina, comme dans un langage à typage statique, nous créons nos types de records personnalisés pour représenter notre modèle de données. Voici comment nous créons les types de record Author, Book et Member :
type Author record {
string firstName;
string lastName;
};
type Book record {
string title;
Author author;
};
type Member record {
string firstName;
string lastName;
int age;
Book[] books;
};
Et avec Ballerina, comme dans les langages à typage dynamique, nous créons des données avec des littéraux de données.
Member kelly = {
firstName: "Kelly",
lastName: "Kapowski",
age: 17,
books: [
{
title: "The Volleyball Handbook",
author: {
firstName: "Bob",
lastName: "Miller"
}
}
]
};
Bien sûr, comme dans un langage traditionnel à typage statique, le système de typage nous permet de savoir quand nous avons manqué un champ dans un record. Notre code ne compilera pas et le compilateur nous dira exactement pourquoi.
Author yehonathan = {
firstName: "Yehonathan"
};
ERROR [...] missing non-defaultable required record field 'lastName'
Dans VSCode, lorsque l'extension Ballerina est installée, vous êtes averti du champ manquant au fur et à mesure que vous tapez.
Maintenant, vous vous demandez probablement si le système de types de Ballerina est statique ou dynamique. Voyons cela de plus près.
Le système de type flexible de Ballerina
Dans un programme orienté données, enrichir les données avec des champs calculés est assez courant. Par exemple, supposons que je veuille enrichir une donnée d'un auteur avec un champ appelé fullName qui contient le nom complet de l'auteur.
Dans un langage traditionnel à typage statique, j'aurais besoin de créer un nouveau type pour cette donnée enrichie, peut-être un nouveau type appelé EnrichedAuthor. Dans Ballerina, ce n'est pas obligatoire; le système de type vous permet d'ajouter des champs au record à la volée, en utilisant la notation entre crochets, comme dans un langage à typage dynamique. Par exemple, voici comment nous ajoutons un champ fullName à un record Author :
Author yehonathan = {
firstName: "Yehonathan",
lastName: "Sharvit"
};
yehonathan["fullName"] = "Yehonathan Sharvit";
Je trouve cette capacité assez étonnante. En un sens, Ballerina nous permet, nous les développeurs, d'avoir notre gâteau et de le manger aussi, en introduisant élégamment une différence sémantique entre deux notations différentes :
-
Lorsque nous utilisons la notation par points pour accéder ou modifier un champ d'un record, Ballerina nous offre la même sécurité et la même aide auxquelles nous sommes habitués dans les langages à typage statique.
-
Lorsque nous utilisons la notation entre crochets pour accéder ou modifier un champ d'un record, Ballerina nous offre la même flexibilité dont nous bénéficions dans les langages à typage dynamique.
Dans certains cas, nous voulons être plus stricts et interdire complètement l'ajout de champs. Pas de problème : Ballerina prend en charge les records fermés. La syntaxe des records fermés est similaire à la syntaxe des records ouverts, sauf que la liste des champs est entourée de deux caractères | .
type ClosedAuthor record {|
string firstName;
string lastName;
|};
ClosedAuthor yehonathan = {
firstName: "Yehonathan",
lastName: "Sharvit"
};
Le système de type ne vous permet pas d'ajouter un champ à un record fermé.
yehonathan["fullName"] = "Yehonathan Sharvit";
ERROR [...] undefined field 'fullName' in 'ClosedAuthor'
Ballerina prend également en charge les champs optionnels dans les records en utilisant le point d'interrogation. Dans le record ci-dessous, le prénom de l'auteur est facultatif.
type AuthorWithOptionalFirstName record {
string firstName?;
string lastName;
};
Lorsque vous accédez à un champ optionnel dans un record, vous devez vous assurer de bien gérer le cas où le champ n'est pas présent. Dans les langages traditionnels à typage dynamique, l'absence d'un vérificateur de type statique fait qu'il est trop facile d'oublier de gérer ce cas. Tony Hoare a introduit les références nulles en 1965 dans un langage de programmation appelé ALGOL, et il l'a considéré plus tard comme une erreur d'un milliard de dollars.
Dans Ballerina, le système de type est là pour vous. Supposons que vous vouliez écrire une fonction qui met en majuscule le prénom d'un auteur.
function upperCaseFirstName(AuthorWithOptionalFirstName author) {
author.firstName = author.firstName.toUpperAscii();
}
Ce code ne compilera pas : le système de type (et l'extension Ballerina VSCode) vous rappellera qu'il n'y a aucune garantie que le champ optionnel soit là.
ERROR [...] undefined function 'toUpperAscii' in type 'string?'
Alors, comment corriger notre code pour gérer correctement l'absence du champ optionnel ? C'est assez simple; après avoir accédé au champ facultatif, vous vérifiez s'il est là ou non. Dans Ballerina, l'absence de champ est représentée par ().
function upperCaseFirstName(AuthorWithOptionalFirstName author) {
string? firstName = author.firstName;
if (firstName is ()) {
return;
}
author.firstName = firstName.toUpperAscii();
}
Notez qu'aucune conversion de type n'est nécessaire. Le système de type est suffisamment intelligent pour comprendre que la variable firstName est garantie d'être une chaîne après avoir vérifié que firstName n'est pas ().
Un autre aspect du système de type Ballerina que je trouve très utile, dans le contexte de la programmation orientée données, est que les types de record ne sont définis que via la structure de leurs champs. Permettez-moi de clarifier.
Lorsque nous écrivons un programme qui manipule des données, la majeure partie de notre base de code est constituée de fonctions qui reçoivent des données et renvoient des données. Chaque fonction a des exigences concernant la forme des données qu'elle reçoit.
Dans les langages à typage statique, ces exigences sont exprimées sous forme de types ou de classes. En regardant une signature de fonction, vous savez exactement quelle est la forme des données des arguments de la fonction. Le problème est que cela crée parfois un couplage étroit entre le code et les données.
Laissez moi vous donner un exemple. Supposons que vous vouliez écrire une fonction qui renvoie le nom complet d'un auteur, vous écririez probablement quelque chose comme ceci :
function fullName(Author author) returns string {
return author.firstName + " " + author.lastName;
}
La limitation de cette fonction est qu'elle ne fonctionne qu'avec des records de type Author. Je trouve un peu décevant que cela ne fonctionne pas avec les records de type Member. Après tout, un record Member comporte également des champs de type chaîne firstName et lastName.
Remarque complémentaire : certains langages à typage statique vous permettent de surmonter cette limitation en créant des interfaces de données.
Les langages à typage dynamique sont beaucoup plus flexibles. En JavaScript, par exemple, vous implémenterez la fonction comme ceci :
function fullName(author) {
return author.firstName + " " + author.lastName;
}
L'argument de la fonction est nommé author, mais en fait, il fonctionne avec n'importe quel élément de données comportant des champs de type chaîne firstName et lastName. Le problème est que lorsque vous transmettez une donnée qui n'a pas l'un de ces champs, vous obtenez une exception d'exécution. De plus, la forme de données attendue des arguments de la fonction n'est pas exprimée dans le code. Ainsi, pour savoir quel type de données la fonction attend, nous devons soit nous fier à la documentation (qui n'est pas toujours à jour), soit investiguer sur le code de la fonction.
Le système de type flexible de Ballerina vous permet de spécifier la forme de vos arguments de fonction, sans compromettre la flexibilité. Vous pouvez créer un nouveau type de record, qui ne mentionne que les champs de record dont la fonction a besoin pour fonctionner correctement.
type Named record {
string firstName;
string lastName;
};
function fullName(Named a) returns string {
return a.firstName + " " + a.lastName;
}
Le système de type flexible de Ballerina vous permet de spécifier la forme des arguments de votre fonction, sans compromettre la flexibilité. Vous pouvez créer un nouveau type de record, qui ne mentionne que les champs de record dont la fonction a besoin pour fonctionner correctement.
type Named record {
string firstName;
string lastName;
};
function fullName(Named a) returns string {
return a.firstName + " " + a.lastName;
}
CONSEIL DE PRO : vous pouvez utiliser un type de record anonyme pour spécifier la forme des arguments de votre fonction.
function fullName(record {
string firstName;
string lastName;
} a)
returns string {
return a.firstName + " " + a.lastName;
}
Vous êtes libre d'appeler votre fonction avec n'importe quel record contenant les champs requis, qu'il s'agisse d'un membre ou d'un auteur, ou de tout autre record contenant les deux champs de chaîne attendus par la fonction.
Member kelly = {
firstName: "Kelly",
lastName: "Kapowski",
age: 17,
books: [
{
title: "The Volleyball Handbook",
author: {
firstName: "Bob",
lastName: "Miller",
fullName: "Bob Miller"
}
}
]
};
fullName(kelly);
// "Kelly Kapowski"
fullName(kelly.books[0].author);
// "Bob Miller"
Voici une analogie que je trouve utile pour illustrer l'approche de Ballerina vis-à-vis des types : les types sont comme des lunettes que nous utilisons dans nos programmes pour regarder la réalité. Mais nous devons nous rappeler que ce que nous voyons à travers nos lentilles n'est qu'un aspect de la réalité. Ce n'est pas la réalité elle-même. Comme le dit l'idiome : la carte n'est pas le territoire.
Par exemple, il n'est pas exact de dire que la fonction fullName -- définie ci-dessus -- reçoit est un record Named. Il est plus exact de dire que la fonction fullName décide d'examiner les données qu'elle reçoit à travers les lentilles d'un record Named.
Prenons un autre exemple. Dans Ballerina, deux records de types différents qui ont exactement les mêmes valeurs de champ sont considérés comme égaux.
Author yehonathan = {
firstName: "Yehonathan",
lastName: "Sharvit"
};
AuthorWithBooks sharvit = {
firstName: "Yehonathan",
lastName: "Sharvit"
};
yehonathan == sharvit;
// true
Au début, ce comportement m'a surpris. Comment deux records de types différents pourraient-ils être considérés comme égaux ? Mais quand j'ai pensé à l'analogie des lunettes, cela m'a semblé logique :
Les deux types sont deux lentilles différentes qui regardent la même réalité. Dans nos programmes, ce qui compte le plus, c'est la réalité, pas les objectifs. Parfois, les langages traditionnels à typage statique semblent mettre davantage l'accent sur les lentilles que sur la réalité.
Jusqu'à présent, nous avons vu comment Ballerina exploite les types afin qu'ils ne nous gênent pas, mais nous aident plutôt à rendre notre workflow de développement plus efficace. Ballerina va encore plus loin et nous permet de manipuler les données de manière puissante et pratique via un langage de requête expressif.
La puissance d'un langage de requête expressif
En tant qu'adepte de la programmation fonctionnelle, mes commandes "pain et beurre" lorsque j'ai besoin de manipuler des données sont constituées de fonctions d'ordre supérieur telles que map, filter et reduce. Ballerina prend en charge la programmation fonctionnelle, mais la manière idiomatique de gérer la manipulation des données dans Ballerina passe par son langage de requête expressif, qui nous permet d'exprimer la logique métier avec éloquence.
Supposons que nous ayons une collection de records et que nous souhaitions uniquement conserver les records qui satisfassent une certaine condition et enrichir ces records avec un champ calculé. Par exemple, imaginons que nous ne souhaitions conserver que les livres dont le titre contient le mot "Volleyball", et les enrichir avec le nom complet de l'auteur.
Voici la fonction qui enrichit le record Author à l'intérieur d'un Book.
function enrichAuthor(Book book) returns Book {
book.author["fullName"] = fullName(book.author);
return book;
}
Nous pourrions utiliser map et filter pour enrichir notre collection de livres, en utilisant map, filter et quelques fonctions anonymes.
function enrichBooks(Book[] books) returns Book[] {
return books.filter(function(Book book) returns boolean {
return book.title.includes("Volleyball");
}).
map(function(Book book) returns Book {
return enrichAuthor(book);
});
}
Mais c'est assez verbeux et un peu ennuyeux de déclarer les types des deux fonctions anonymes. En utilisant le langage de requête Ballerina, le code est plus compact et plus facile à lire.
function enrichBooks(Book[] books) returns Book[] {
return from var book in books
where book.title.includes("Volleyball")
select enrichAuthor(book);
}
Le langage de requête Ballerina sera traité plus en détail dans notre série Ballerina.
Avant d'aller de l'avant et de parler de JSON, écrivons un petit test unitaire pour notre fonction. Dans Ballerina, les records sont considérés comme égaux lorsqu'ils ont les mêmes champs et valeurs. Ainsi, il est simple de comparer les données renvoyées par une fonction avec les données que nous attendons.
Book bookWithVolleyball = {
title: "The Volleyball Handbook",
author: {
firstName: "Bob",
lastName: "Miller"
}
};
Book bookWithoutVolleyball = {
title: "Friendship Bread",
author: {
firstName: "Darien",
lastName: "Gee"
}
};
Book[] books = [bookWithVolleyball, bookWithoutVolleyball];
Book[] expectedResult = [
{
title: "The Volleyball Handbook",
author: {
firstName: "Bob",
lastName: "Miller",
fullName: "Bob Miller"
}
}
];
enrichBooks(books) == expectedResult;
// true
CONSEIL DE PRO : Ballerina est livré avec un framework de test unitaire out of the box.
Maintenant que nous avons vu la flexibilité et la facilité que Ballerina offre autour de la représentation et de la manipulation des données dans un programme, voyons comment Ballerina nous permet d'échanger des données avec d'autres programmes.
Prise en charge de JSON out-of-the-box
JSON est probablement le format le plus populaire pour l'échange de données. Assez souvent, les programmes impliqués dans les systèmes d'information communiquent en s'envoyant des chaînes JSON. Lorsqu'un programme doit envoyer des données sur le réseau, il sérialise une structure de données dans une chaîne JSON. Et lorsqu'un programme reçoit une chaîne JSON, il doit l'analyser pour la convertir en une structure de données.
Ballerina, étant un langage conçu pour l'ère du cloud, prend en charge la sérialisation JSON et l'analyse JSON out of the box. Tout record peut être sérialisé dans une chaîne JSON, comme illustré ici :
AuthorWithBooks yehonathan = {
firstName: "Yehonathan",
lastName: "Sharvit",
numOfBooks: 1
};
yehonathan.toJsonString();
// {"firstName":"Yehonathan", "lastName":"Sharvit", "numOfBooks":1}
À l'opposé, une chaîne JSON peut être analysée dans un record. Ici, nous devons être prudents et nous assurer que nous traitons les cas où la chaîne JSON n'est pas une chaîne JSON valide ou n'est pas conforme à la forme de données que vous attendez.
function helloAuthor(string authorStr) returns error? {
Author|error author = authorStr.fromJsonStringWithType();
if (author is error) {
return author;
} else {
io:println("Hello, ", author.firstName, "!");
}
}
CONSEIL DE PRO : Ballerina accepte les erreurs et nous permet d'écrire succinctement la même logique de manière plus compacte via une construction de vérification spéciale.
function helloAuthor(string authorStr) returns error? {
Author author = check authorStr.fromJsonStringWithType();
io:println("Hello, ", author.firstName, "!");
}
Remarque : la prise en charge de JSON dans Ballerina va bien au-delà de la sérialisation et de l'analyse. En fait, Ballerina est livré avec un type json qui vous permet de manipuler des données exactement comme dans un langage dynamique. JSON avancé dans Ballerina sera couvert plus tard dans notre série sur Ballerina.
Nous avons exploré les avantages que Ballerina offre en matière de représentation, de manipulation et de communication de données. Nous allons conclure notre exploration avec un exemple de mini programme orienté données qui illustre ces avantages.
Dernier exemple : manipuler des données avec facilité et confiance
Imaginez que nous construisons un système de gestion de bibliothèque composé de plusieurs programmes qui échangent des données sur les membres, les livres et les auteurs. L'un des programmes est amené à traiter les données des membres, en les enrichissant avec des champs calculés du nom complet du membre, en ne conservant que les livres dont les titres contiennent "Volleyball" et en ajoutant le nom complet de l'auteur à chaque livre.
Le programme communique sur le réseau en utilisant JSON : il reçoit les données des membres au format JSON et doit les renvoyer au format JSON.
Voici à quoi ressemblerait le code de ce programme avec Ballerina.
Tout d'abord, nous créons nos types de records personnalisés.
type Author record {
string firstName;
string lastName;
};
type Book record {
string title;
Author author;
};
type Member record {
string firstName;
string lastName;
int age;
Book[] books?; // books is an optional field
};
Ensuite, une petite fonction utilitaire qui calcule le nom complet de tout record contenant des champs de chaîne firstName et lastName. Nous exprimons cette contrainte à l'aide d'un record anonyme.
function fullName(record {
string firstName;
string lastName;
} a)
returns string {
return a.firstName + " " + a.lastName;
}
Nous utilisons le langage de requête Ballerina pour filtrer et enrichir les livres :
- Ne conservez que les livres dont le titre contient « Volleyball »
- Enrichir chaque livre avec le nom complet de l'auteur
function enrichAuthor(Author author) returns Author {
author["fullName"] = fullName(author);
return author;
}
function enrichBooks(Book[] books) returns Book[] {
return from var {author, title} in books
where title.includes("Volleyball") // filter books whose title include Volleyball
let Author enrichedAuthor = enrichAuthor(author) // enrich the author field
select {author: enrichedAuthor, title: title}; // select some fields
Maintenant, nous écrivons notre logique métier : une fonction qui enrichit un record Member avec :
- Le nom complet du membre
- Les livres filtrés et enrichis
function enrichMember(Member member) returns Member {
member["fullName"] = fullName(member); // fullName works on member and authors
Book[]? books = member.books; // books is an optional field,
if (books is ()) { // handle explicitly the case where the field is not present
return member;
}
// the type system is smart enough to understand that here books is guaranteed to be an array
member.books = enrichBooks(books);
return member;
}
Enfin, nous écrivons le point d'entrée du programme qui fait ce qui suit :
- Analyser l'entrée JSON dans un record de type Member
- Appeler la fonction qui traite la logique métier pour obtenir un record de type Member enrichi
- Sérialiser le résultat en JSON
Notez que nous devons gérer le fait que la chaîne JSON que nous recevons est invalide. Voici comment procéder :
- Nous déclarons que la valeur de retour peut être soit une chaîne soit une erreur.
- Nous appelons check sur ce qui est retourné par fromJsonStringWithType. Ballerina propage automatiquement une erreur, au cas où la chaîne JSON que nous avons reçue n'est pas valide.
function entryPoint(string memberJSON) returns string|error {
Member member = check memberJSON.fromJsonStringWithType();
var enrichedMember = enrichMember(member);
return enrichedMember.toJsonString();
}
C'est tout pour le code qui traite de la logique elle-même. Vous pouvez trouver le code complet sur GitHub.
Afin d'en faire une véritable application, j'utiliserais l'un des nombreux protocoles prêts à l'emploi que Ballerina fournit pour communiquer sur le réseau, comme HTTP, GraphQL, Kafka, gRPC, WebSockets, etc.
Conclusion
En travaillant sur les extraits de code présentés dans cet article, j'ai eu l'impression de revivre la sensation agréable que mon IDE m'apportait lorsque je travaillais sur des langages à typage statique. J'ai été surpris de découvrir que pour profiter de cette expérience, cette fois je n'avais pas à faire de compromis sur la puissance d'expression et la flexibilité auxquelles j'étais devenu accro depuis que j'avais commencé à travailler avec des langages à typage dynamique.
La principale chose qui me manque dans Ballerina est la possibilité de mettre à jour une donnée sans la faire muter, comme j'en ai l'habitude en programmation fonctionnelle. Je n'ai pas pu implémenter cette fonctionnalité en tant que fonction personnalisée dans Ballerina, car elle nécessite la prise en charge de la gestion des types génériques. Mais j'espère que dans un avenir proche cette capacité sera ajoutée au langage.
Je vois Ballerina comme un langage de programmation à usage général, dont l'approche des données en fait un excellent choix pour la construction de systèmes d'information. À mon avis, cela est dû aux valeurs clés de Ballerina concernant la représentation des données, la manipulation des données et la communication des données.
- Il traite les données comme un citoyen de premier ordre
- Son système de typage flexible offre plus de flexibilité que les langages traditionnels à typage statique, sans compromettre la sécurité et l'outillage
- Son système de typage flexible offre plus d'outils et de sécurité que les langages à typage dynamique, sans compromettre la vitesse et la puissance d'expression
- Il a un langage de requête expressif pour la manipulation des données
- Il prend en charge JSON out of the box pour échanger des données sur le réseau
Vous pouvez en savoir plus sur Ballerina en visitant ballerina.io.
Dans les prochains articles de notre série sur Ballerina, nous couvrirons des aspects supplémentaires de Ballerina, comme les tables, les requêtes avancées, la gestion des erreurs, les maps, le type json, les connecteurs, et plus encore... Vous pouvez vous inscrire à notre newsletter pour être averti lorsque le prochain article de la série sur Ballerina sera publié.