introduction
Récemment, les inconvénients des architectures orientées services et, en particulier, des architectures microservices (MA) ont été activement discutés. Il y a quelques années à peine, beaucoup étaient prêts à migrer vers MA en raison de ses nombreux avantages: flexibilité sous la forme de déploiements indépendants, propriété transparente, stabilité accrue du système et meilleure séparation des préoccupations. Cependant, la situation a récemment changé: l'approche des microservices a été critiquée pour sa tendance à augmenter considérablement la complexité, ce qui rend parfois difficile la mise en œuvre de fonctions même triviales . (Nous en avons parlé dans la conférence " Microservices: la taille compte, même si vous avez Kubernetes " - environ Transl.)
Uber dispose actuellement d'environ 2200 microservices critiques, et nous avons nous-mêmes expérimenté tous les avantages et inconvénients de cette approche. Au cours des deux dernières années, Uber a tenté de réduire la confusion du paysage des microservices tout en conservant les avantages de l'architecture en cours de route. Avec cet article, nous prévoyons de présenter notre approche générique des architectures de microservices appelée DOMA (Domain-Oriented Microservice Architecture).
S'il a été populaire de critiquer les architectures de microservices pour leurs lacunes ces dernières années, peu ont osé proclamer qu'elles devraient être entièrement abandonnées. Leurs avantages opérationnels ne sont que trop importants; de plus, il ne semble pas y avoir d'alternatives (ou extrêmement limitées) à cette approche. Le but de notre approche générique est d'aider les organisations qui souhaitent réduire la complexité globale du système tout en maintenant la flexibilité de l'AG.
Cet article explorera DOMA, les défis qui ont conduit à cette approche dans Uber, ses avantages pour les équipes plateforme et produit, et enfin quelques astuces pour ceux qui cherchent à migrer vers cette architecture.
Qu'est-ce qu'un microservice?
Les microservices sont une extension des architectures orientées services. Contrairement aux «services» assez volumineux des années 2000, les microservices effectuent une tâche spécifique. Ces applications sont hébergées et accessibles sur le réseau et fournissent une interface bien définie. D'autres applications accèdent à cette interface à l'aide d' un appel de procédure à distance (RPC).
Une caractéristique clé de MA est la manière dont le code est publié, appelé et déployé. Les grandes applications monolithiques sont généralement divisées en composants encapsulés avec des interfaces bien définies. Ces interfaces sont alors appelées directement à partir du processus plutôt que sur le réseau. En ce sens, un microservice peut être considéré comme une sorte de bibliothèque avec des performances inférieures (en raison de l'effet des retards du réseau et du temps sur la sérialisation / désérialisation) lors de l'appel de l'une de ses fonctions.
En pensant aux microservices de cette manière, nous pourrions nous demander pourquoi nous avons besoin d'une architecture de microservices? La réponse classique à cette question est due à la possibilité de déployer indépendamment des composants individuels et de les mettre à l' échelle facilement.... Dans le cas d'une application monolithique de grande taille, l'organisation est obligée de déployer ou de publier tout le code en même temps. En conséquence, chaque nouvelle version comporte de nombreux changements. Les déploiements deviennent risqués et prennent du temps. Toute erreur peut faire tomber tout le système.
Ainsi, les entreprises se tournent vers les microservices pour une facilité d' utilisation tout en sacrifiant les performances . Ils doivent également supporter les coûts supplémentaires de maintenance de l'infrastructure requise pour les microservices. L'expérience montre que dans de nombreuses situations, un tel compromis a du sens. Dans le même temps, c'est un argument de poids contre une transition prématurée vers l'AMM.
Motivation
Au moment de la transition vers les microservices (vers 2012-2013), chez Uber, nous avions deux principaux services monolithiques, et nous avons été confrontés à de nombreux problèmes opérationnels que les microservices résolvent avec succès:
- Risques de disponibilité. Toute erreur dans la base de code du monolithe peut supprimer tout le système (dans ce cas, tout Uber).
- Déploiements risqués et coûteux. Ils étaient très difficiles à réaliser et devaient souvent revenir à la version précédente.
- Mauvaise séparation des domaines de responsabilité. Il était très difficile de savoir qui était responsable de quoi dans la base de code colossale. Avec une croissance exponentielle, la précipitation a parfois brouillé les frontières entre la logique et les composants.
- Travail inefficace. Les problèmes susmentionnés ont rendu difficile pour les équipes de travailler indépendamment ou indépendamment les unes des autres.
En d'autres termes, dans le contexte de l'augmentation du nombre d'ingénieurs chez Uber de dizaines à des centaines de personnes et de l'émergence d'un grand nombre d'équipes propriétaires de leurs parties de la pile technologique, l'architecture monolithique liait de plus en plus le sort de ces équipes et ne leur permettait pas de travailler de manière indépendante.
Par conséquent, nous avons décidé de passer à MA. En conséquence, nos systèmes sont devenus plus flexibles et ont permis aux équipes de devenir plus autonomes .
- Fiabilité du système. La fiabilité globale du système augmente avec la transition vers MA. Un service individuel peut planter (et peut être restauré à une version précédente) sans risquer de planter tout le système.
- . - : « ?», — .
- . , . , , , , .
- . .
- . .
Il n'est pas exagéré de dire qu'Uber n'aurait pas pu atteindre son échelle et son niveau de qualité actuels sans MA.
Cependant, alors que l'entreprise continuait de croître et que le nombre d'ingénieurs passait de centaines à des milliers, nous avons commencé à remarquer un certain nombre de problèmes associés à la complexité considérablement accrue du système. Dans le cas de MA, nous sacrifions une seule base de code monolithique en échange d'un certain nombre de «boîtes noires» dont la fonctionnalité peut changer à tout moment et conduire à des comportements inattendus.
Par exemple, les ingénieurs ont dû analyser environ 50 services dans 12 équipes différentes pour aller à la racine d'un problème.
Comprendre les dépendances entre les services peut devenir assez difficile car ils peuvent interagir les uns avec les autres à plusieurs niveaux. Un pic de retards dans la n-ième dépendance peut provoquer une avalanche de problèmes dans les services en amont. De plus, sans les outils appropriés, il sera impossible de comprendre ce qui s'est passé. Tout cela rend le débogage très difficile.
Architecture de microservices d'Uber à partir de mi-2018 par Jaeger
Pour mettre en œuvre la fonction la plus simple, un ingénieur doit souvent travailler avec de nombreux services, alors que des équipes et des personnes complètement différentes en sont responsables. En conséquence, beaucoup de temps est passé à organiser le travail d'équipe, les réunions, les consultations de conception et la révision du code (examen de base). L'avantage initial de la transparence de la propriété s'estompe progressivement à mesure que les équipes envahissent continuellement les services des autres, modifient les modèles de données et même déploient pour le compte des propriétaires de services. Cela peut créer des monolithes de réseau dans lesquels les services ne semblent être indépendants, mais en fait, ils doivent être déployés ensemble afin d'effectuer tout changement en toute sécurité.
Un exemple d'un système aussi complexe dans Uber (~ 2018) avec dix points de contact pour une intégration facile (même avant DOMA).
En conséquence, nous avons un ralentissement du processus de développement, une instabilité qui frappe les propriétaires de services, des migrations plus longues, etc. Hélas, il n'y a pas de retour en arrière pour les organisations qui sont déjà passées à MA. La situation est parfaitement illustrée par la phrase bien connue: " Il est impossible de vivre avec eux, et on ne peut pas leur tirer dessus ".
Architecture de microservice spécifique au domaine
Considérez les microservices comme des bibliothèques liées aux E / S et l'architecture des microservices comme une énorme application distribuée. Dans ce cas, nous pouvons utiliser des solutions architecturales bien connues pour réfléchir à la meilleure façon d'organiser notre code.
Ainsi, une architecture de microservice orientée domaine (DOMA) peut s'appuyer sur des méthodes bien établies d'organisation du code telles que la conception orientée domaine , l' architecture propre, l'architecture orientée services , les modèles de conception orientés objet et orientés interface.Nous considérons DOMA comme innovant en ce sens qu'il s'agit d'un moyen relativement nouveau d'exploiter les principes de conception existants dans les systèmes distribués à l'échelle mondiale des grandes organisations .
Voici quelques concepts DOMA de base et la terminologie associée:
- Au lieu de regarder des microservices individuels, nous examinons des groupes d'entre eux. Et nous les appelons des domaines (domaines) .
- Ensuite, nous combinons les domaines des soi-disant couches (les couches) . La couche à laquelle appartient un domaine détermine les dépendances disponibles pour les microservices de ce domaine. Nous appelons l'architecture résultante du multicouche (de la conception des couches) .
- , . (gateways).
- , , , 'hardcode' , . (, - ), (extension architecture) .
En d'autres termes, l'architecture structurée, les passerelles de domaine et les points d'extensibilité DOMA prédéfinis transforment les architectures de microservices de quelque chose de complexe à quelque chose de compréhensible et tangible: un ensemble structuré de composants flexibles, réutilisables et en couches.
Le reste de cet article se concentrera sur l'implémentation de DOMA par Uber et ses avantages. Des conseils pratiques seront également donnés aux entreprises souhaitant adopter cette approche.
Implémentation dans Uber
Domaines
Les domaines Uber sont des collections d'un ou de plusieurs microservices liés en fonction d'une combinaison logique de fonctionnalités. La question se pose naturellement, quelle devrait être la taille du domaine. Dans ce cas, nous ne donnons aucune instruction. Certains domaines peuvent inclure des dizaines de services, d'autres un seul. Il est important ici de réfléchir attentivement au rôle logique de chaque association. Par exemple, nous avons regroupé les services de recherche sur la carte, les services tarifaires, les services de sélection (comparaison des conducteurs et des passagers) dans des domaines distincts. De plus, ils ne répètent pas toujours la structure organisationnelle de l'entreprise. Uber Maps est divisé en trois domaines avec 80 microservices cachés derrière trois passerelles différentes.
Architecture basée sur les couches
L'architecture multicouche répond à la question de savoir quel service et lequel peut communiquer dans les limites de MA Uber. Autrement dit, il peut être considéré comme une répartition mondiale des domaines de responsabilité ou comme un mécanisme de gestion globale de la dépendance.
L'architecture en couches permet de comprendre le rayon des dommages après des pannes et reflète la spécificité du produit en termes de nombre de services dépendants Uber. Au fur et à mesure que vous passez du bas vers le haut, le nombre de services affectés en cas de panne est réduit et le champ d'application du produit se rétrécit . Et vice versa, un plus grand nombre de services dépend de la fonctionnalité aux niveaux inférieurs, de sorte que le rayon des dommages résultant d'une défaillance est, en règle générale, plus grand et la gamme des tâches commerciales à résoudre est plus large. La figure ci-dessous illustre ce concept.
Vous pouvez imaginer que les niveaux supérieurs se concentrent sur les fonctions responsables d'une expérience utilisateur spécifique (étroite) (par exemple, les fonctions mobiles), tandis que les niveaux inférieurs sont habités par des fonctions commerciales plus globales (par exemple, la gestion de compte ou les déplacements sur le marché du covoiturage) ... Chaque couche dépend uniquement des couches sous-jacentes, ce qui clarifie des concepts tels que le rayon de l'explosion et l'intégration de domaine.
Il convient de noter que la fonctionnalité se déplace souvent vers le bas dans ce graphique, de plus étroit à plus large. Vous pouvez imaginer une fonction simple qui devient plus importante («plateforme») avec le temps à mesure que les exigences évoluent. En fait, ce type de migration vers le bas est attendu, et bon nombre des principales plates-formes commerciales d'Uber ont commencé comme une fonctionnalité pour les conducteurs ou les passagers, et au fil du temps, elles ont grandi et se sont généralisées en tant que nouveaux secteurs d'activité (tels que Uber Eats ou Uber Freight. ) et connectez-leur plus de dépendances.
Au sein d'Uber, nous distinguons les cinq niveaux suivants.
- . , . — Uber , .
- -. , Uber , , Rides (), Eats ( ) Freight ( ).
- . , , . , «request a ride» ( ) , Rides: Rider, Rider «Lite», m.uber.com, ..
- . , (/), .
- . Uber . .
Comme vous pouvez le voir, chaque niveau suivant représente une combinaison de fonctions de plus en plus étroite et a un rayon de frappe plus petit (en d'autres termes, moins de composants dépendent des fonctionnalités de cette couche).
Passerelles
Le terme passerelle API est déjà bien établi dans les architectures de microservices. Notre définition n'est pas très différente de celle bien établie - sauf que nous avons tendance à considérer les passerelles comme un point d'entrée unique dans le groupe de services correspondant (que nous appelons un domaine ). Le succès d'une passerelle dépend d'une architecture d'API bien conçue:
ce diagramme illustre la conception de haut niveau d'une passerelle. Il fait abstraction des détails de la structure interne des domaines: un ensemble de services, des tables avec des données, des pipelines ETL, etc. Les autres domaines ont accès uniquement aux interfaces: API pour les appels de procédure à distance, les événements et les requêtes dans la messagerie.
Étant donné que les consommateurs en amont ne fonctionnent que sur un seul service, les passerelles offrent de nombreux avantages en termes de migrations futures , de découvrabilité et de réduction globale de la complexité du système lorsque les services en amont n'ont qu'une seule dépendance (au lieu de dépendre de plusieurs services en aval qui peut exister dans le domaine). Vu du point de vue de la conception OO, les passerelles sont des définitions d'interface et nous permettent de faire tout ce que nous voulons avec une «implémentation» interne (c'est-à-dire un groupe de microservices).
Extensions
Les extensions (extensions) , comme leur nom l'indique, est un mécanisme pour développer des domaines. La définition de base d'un tel module complémentaire est qu'il fournit un mécanisme pour étendre la fonctionnalité d'un service sans modifier les éléments internes de ce service ou affecter sa fiabilité globale. Dans notre Uber a deux modèles d'expansion: la logique (extensions logiques) et sur la base de données (données les extensions) . Le concept d'extension nous a permis de faire évoluer l'architecture afin que plusieurs équipes puissent travailler indépendamment les unes des autres.
Extensions logiques
Les extensions logiques fournissent un mécanisme pour étendre la logique sous-jacente d'un service. Pour eux, nous utilisons une sorte de modèle de fournisseur ou de plugin avec une interface qui est définie séparément pour chaque service. Cela permet aux équipes d'implémenter leur logique en utilisant uniquement l'interface et sans interférer avec le code de la plateforme principale.
Supposons, par exemple, que le pilote soit en ligne. Nous effectuons généralement diverses vérifications pour nous assurer qu'ils sont autorisés à avoir un statut en ligne (sécurité, conformité, etc.). Chacun d'eux a sa propre équipe. Une manière possible de le faire est de forcer chaque commande à écrire la logique au même point de terminaison, mais cela peut ajouter de la complexité. Chaque vérification nécessitera une logique différente - et totalement indépendante.
Dans le cas d'extensions de point de terminaison logiques appelées aller en lignedéfinira l'interface à laquelle chaque extension est censée se conformer avec une demande et un type de réponse prédéfinis. Chaque équipe enregistrera une extension qui sera responsable de la mise en œuvre de cette logique. Dans ce cas, ils peuvent simplement prendre quelques informations sur le pilote et renvoyer une valeur logique (booléenne) , qui déterminera si le pilote est "digne" du statut en ligne ou non. Et le point final lui-même (aller en ligne) va simplement itérer sur ces réponses et établir si l'une d'entre elles est fausse .
Cette approche sépare le code principal des extensions et fournit une isolation entre elles. Cependant, les extensions ne savent pas quelle autre logique est en cours d'exécution. Cela facilite la création de fonctionnalités supplémentaires, par exemple pour l'observabilité ou le marquage des fonctionnalités .
Extensions basées sur les données
Ce type d'extension fournit un mécanisme pour attacher des données arbitraires à l'interface pour éviter de gonfler inutilement les modèles de données de la plate-forme sous-jacente. Dans les extensions de données, nous utilisons activement des fonctionnalités telles que Any de Protobuf, qui permettent d'ajouter des données arbitraires aux requêtes. Les services stockent souvent ces données ou les transmettent à une extension logique, de sorte que la plate-forme principale ne désérialise jamais (et ne "sache" donc rien) de ce contexte arbitraire. Toute implémentation entraîne une surcharge d'infrastructure en échange d'un typage plus fort. Une alternative plus simple est le format JSON pour représenter toutes les données:
Compléments arbitraires
En plus des extensions booléennes et de données, de nombreuses équipes d'Uber ont développé des modèles d'extensions personnalisés pour correspondre à leurs domaines. Par exemple, la plupart des intégrations liées à l'architecture de présentation utilisent une logique d'exécution de tâches basée sur DAG.
Avantages
DOMA a influencé presque toutes les grandes entreprises Uber à un degré ou à un autre. Au cours de l'année écoulée, nous nous sommes principalement concentrés sur la couche commerciale. Il fournit une logique généralisée pour les différents secteurs d'activité d'une entreprise.
DOMA est relativement nouveau sur Uber, et à l'avenir, nous partagerons certainement plus d'informations et d'exemples de notre architecture. Les premiers résultats sont encourageants: ils simplifient grandement le travail des développeurs et réduisent la complexité globale du système.
Produits et plateformes
DOMA est le résultat d'un effort de collaboration entre les différentes équipes produit et plateforme d'Uber. Dans de nombreux cas, les coûts de support de la plate-forme ont chuté d'un ordre de grandeur. Les équipes produits ont bénéficié d'une spécificité et d'un développement accéléré.
Par exemple, un des premiers utilisateurs de la plate-forme de notre architecture d'extension a été en mesure de réduire le temps de priorisation et d'intégrer une nouvelle fonctionnalité de trois jours à trois heures en réduisant les temps de révision du code, la planification et en accélérant l'éducation des consommateurs.
Complexité réduite
Auparavant, les équipes produit devaient travailler avec de nombreux services en aval au sein d'un domaine, mais elles n'ont plus qu'à en appeler un. En réduisant le nombre de points de contact lors de l'introduction d'une nouvelle fonctionnalité, le temps de mise en œuvre a été réduit de 25 à 30%. De plus, nous avons pu distribuer 2 200 services dans 70 domaines. Environ la moitié d'entre eux ont été mis en œuvre et, pour la majorité, il existe un plan de mise en œuvre sous une forme ou une autre.
Migrations futures
Chez Uber, nous avons calculé que le microservice a une demi-vie de 1,5 an. En d'autres termes, chaque année et demie, 50% de nos services perdent de leur pertinence. Sans passerelles, une architecture de microservice peut devenir un enfer de migration. Les microservices en constante évolution nécessitent des migrations en amont constantes. Les passerelles permettent aux équipes d'éviter les dépendances sur les services de domaine en aval, ce qui signifie que ces services peuvent changer sans avoir à migrer vers l'amont.
Deux des plus grandes mises à niveau de plate-forme d'Uber au cours de l'année écoulée ont eu lieu derrière des passerelles. Ces plates-formes ont des centaines de services dépendants, et sans passerelles, tous les consommateurs existants devraient être migrés. Ce serait incroyablement coûteux, ce qui rendrait irréaliste une refonte complète de la plate-forme.
Nouveaux métiers et produits
Les frameworks basés sur DOMA se sont avérés beaucoup plus extensibles et plus faciles à maintenir. La plupart des équipes d'Uber qui sont passées à DOMA l'ont fait car il devenait trop coûteux de maintenir de nouvelles activités.
Conseils pratiques
Dans cette section, j'ai compilé quelques conseils pratiques pour les entreprises qui pourraient être intéressées par DOMA. Le principe directeur ici est que, d'après notre expérience, une architecture de microservices mature et réfléchie est basée sur des changements progressifs dans la bonne direction au bon moment. En réalité, il est presque impossible de «réécrire» complètement MA.
Par conséquent, nous considérons l'évolution de l'AM comme une sorte de processus de «taille d'une haie», grâce auquel elle pousse dans la bonne direction, et non comme un effort volontaire ponctuel. C'est un processus dynamique et progressif.
Les startups
Les questions clés ici sont: "Quand devrions-nous passer à l'AM?" et "Est-ce que cela a du sens pour notre organisation?" Comme nous l'avons vu ci-dessus, si les microservices offrent un avantage opérationnel dans les organisations comptant un grand nombre d'ingénieurs, ils ajoutent également à la complexité globale qui peut rendre difficile la mise en œuvre de nouvelles fonctionnalités.
Dans les petites organisations, il est peu probable que l'avantage opérationnel compense la complexité architecturale accrue. De plus, les AM ont généralement besoin de ressources d'ingénierie dédiées pour les soutenir, ce qui peut être trop coûteux pour une entreprise en démarrage ou tout simplement sous-optimal en termes de hiérarchisation.
Cela dit, il peut être judicieux de reporter la transition vers les microservices pendant un certain temps. Si l'organisation décide de passer aux microservices, nous lui recommandons d'utiliser l'analogie d'une grande application distribuée et de penser à l'avance à la division des zones problématiques entre les services. Gardez également à l'esprit que les premiers microservices sont probablement les plus importants et les plus durables, car ils décrivent un élément clé de l'entreprise.
Entreprise moyenne
L'utilité de MA augmente dans les entreprises de taille moyenne avec de nombreuses équipes, où les lignes de responsabilité s'estompent progressivement entre les différentes fonctions et plateformes.
C'est là que vous pouvez commencer à réfléchir à la hiérarchie des microservices. La gestion des dépendances peut être mise en avant car certains services peuvent devenir beaucoup plus critiques pour la gestion d'une entreprise et davantage d'équipes compteront sur eux.
Les premiers investissements dans la plateforme peuvent rapporter des dividendes plus tard. La création de plateformes métiers qui ne dépendent pas d'autres produits permet d'éviter l'accumulation de dettes techniques et la pénétration d'une logique produit arbitraire dans les principaux services de la plateforme. Peut-être qu'à ce stade, un mécanisme d'extension devrait être introduit pour atteindre cet objectif.
Étant donné que le nombre de microservices est encore faible, il peut ne pas être judicieux de les regrouper pour le moment. Cependant, il convient de noter ici qu'un domaine dans le contexte de l'implémentation DOMA dans Uber peut bien inclure un seul service, donc un train de pensée «orienté domaine» ne fait toujours pas de mal.
Grosse affaire
Les grandes organisations d'ingénierie peuvent avoir des centaines de spécialistes, des microservices et de nombreuses dépendances. C'est dans ces conditions que DOMA atteint son plein potentiel. Il est certain que ces entreprises auront des grappes évidentes de microservices qui peuvent être facilement combinées en domaines avec des passerelles devant elles. Les services hérités nécessitent souvent une refactorisation / réécriture et une migration ultérieure. Cela signifie que les passerelles commenceront bientôt à apporter de réels avantages en termes de facilité de migration (si, bien sûr, elles sont déjà déployées).
L'importance d'une hiérarchie transparente et compréhensible augmentera également: certains services seront des «produits» pour certaines fonctions ou groupes de fonctions, tandis que d'autres prendront en charge plusieurs produits et agiront comme des «plates-formes». À ce stade, il est essentiel de séparer la logique produit arbitraire des plates-formes pour éviter un stress opérationnel massif sur les équipes de plates-formes et pour minimiser le risque d'instabilité globale du système.
Dernières pensées
Chez Uber, nous continuons à développer activement DOMA à mesure que de plus en plus d'équipes y migrent. L'idée principale derrière DOMA est qu'une architecture de microservice n'est qu'un grand programme distribué. Et les mêmes principes peuvent être appliqués à son évolution comme à tout autre logiciel. DOMA n'est qu'une approche de réflexion pratique sur ces principes. Nous espérons que vous le trouverez utile et attendons vos commentaires avec impatience!
DOMA lui-même est le résultat d'un effort interfonctionnel de près de 60 ingénieurs d'Uber. Je tiens à exprimer ma gratitude particulière aux personnes suivantes pour leur contribution à ce travail au cours des 2 dernières années:
Alex Zylman, Alexandre Wilhelm, Allen Lu, Ankit Srivastava, Anthony Tran, Anupam Dikshit, Anurag Biyani, Daniel Wolf, Deepti Chedda, Dmitriy Bryndin, Gaurav Tungatkar, Jacob Greenleaf, Jaikumar Ganesh, Jennie Ngyabuae, Joeoshin , Kusha Kapoor, Linda Fu, Madan Thangavelu, Nimish Sheth, Parth Shah, Shawn Burke, Simon Newton, Steve Sherwood, Uday Kiran Medisetty et Waleed Kadous.
Remerciements: Ce travail a combiné de nombreux modèles de conception existants dans l'industrie pour résoudre des problèmes dans Uber, et a également suggéré de nouveaux modèles (comme des extensions). Nous sommes reconnaissants à l’industrie d’y travailler. Nous remercions également les ingénieurs de Linkedin qui ont travaillé sur les Superblocks pour avoir partagé leurs expériences avec nous.
PS du traducteur
Lisez aussi sur notre blog:
- «: , Kubernetes»;
- « 2018 ».