Event Ruler (appelé Ruler dans le reste de la documentation par souci de concision) est une bibliothèque Java qui permet de faire correspondre Rules to Events . Un événement est une liste de champs, qui peuvent être donnés sous forme de paires nom/valeur ou sous forme d'objet JSON. Une règle associe les noms de champs d'événements à des listes de valeurs possibles. Il y a deux raisons d'utiliser Ruler :
Contenu:
C'est plus simple à expliquer par un exemple.
Un événement est un objet JSON. Voici un exemple :
{
"version" : "0" ,
"id" : "ddddd4-aaaa-7777-4444-345dd43cc333" ,
"detail-type" : "EC2 Instance State-change Notification" ,
"source" : "aws.ec2" ,
"account" : "012345679012" ,
"time" : "2017-10-02T16:24:49Z" ,
"region" : "us-east-1" ,
"resources" : [
"arn:aws:ec2:us-east-1:123456789012:instance/i-000000aaaaaa00000"
] ,
"detail" : {
"c-count" : 5 ,
"d-count" : 3 ,
"x-limit" : 301.8 ,
"source-ip" : "10.0.0.33" ,
"instance-id" : "i-000000aaaaaa00000" ,
"state" : "running"
}
}
Vous pouvez également voir cela comme un ensemble de paires nom/valeur. Par souci de concision, nous ne présentons qu’un échantillon. Ruler dispose d'API pour fournir des événements à la fois sous forme JSON et sous forme de paires nom/valeur :
+--------------+------------------------------------------+
| name | value |
|--------------|------------------------------------------|
| source | "aws.ec2" |
| detail-type | "EC2 Instance State-change Notification" |
| detail.state | "running" |
+--------------+------------------------------------------+
Les événements au format JSON peuvent être fournis sous la forme d'une chaîne JSON brute ou d'un Jackson JsonNode analysé.
Les règles de cette section correspondent toutes à l'exemple d'événement ci-dessus :
{
"detail-type" : [ "EC2 Instance State-change Notification" ] ,
"resources" : [ "arn:aws:ec2:us-east-1:123456789012:instance/i-000000aaaaaa00000" ] ,
"detail" : {
"state" : [ "initializing" , "running" ]
}
}
Cela fera correspondre n'importe quel événement avec les valeurs fournies pour les valeurs resource
, detail-type
et detail.state
, en ignorant tous les autres champs de l'événement. Cela correspondrait également si la valeur de detail.state
avait été "initializing"
.
Les valeurs des règles sont toujours fournies sous forme de tableaux et correspondent si la valeur de l'événement est l'une des valeurs fournies dans le tableau. La référence aux resources
montre que si la valeur de l'événement est également un tableau, la règle correspond si l'intersection entre le tableau d'événements et le tableau de règles n'est pas vide.
{
"time" : [ { "prefix" : "2017-10-02" } ]
}
Les correspondances de préfixe ne fonctionnent que sur les champs de valeur chaîne.
{
"source" : [ { "prefix" : { "equals-ignore-case" : "EC2" } } ]
}
Les correspondances de préfixe égal à la casse ne fonctionnent que sur les champs de valeur chaîne.
{
"source" : [ { "suffix" : "ec2" } ]
}
Les correspondances de suffixe ne fonctionnent que sur les champs de valeur chaîne.
{
"source" : [ { "suffix" : { "equals-ignore-case" : "EC2" } } ]
}
Les correspondances de suffixe égal à la casse ne fonctionnent que sur les champs de valeur chaîne.
{
"source" : [ { "equals-ignore-case" : "EC2" } ]
}
Les correspondances égales à la casse ne fonctionnent que sur les champs de valeur chaîne.
{
"source" : [ { "wildcard" : "Simple*Service" } ]
}
Les correspondances génériques ne fonctionnent que sur les champs de valeur chaîne. Une valeur unique peut contenir de zéro à plusieurs caractères génériques, mais les caractères génériques consécutifs ne sont pas autorisés. Pour faire correspondre spécifiquement le caractère astérisque, un caractère générique peut être échappé avec une barre oblique inverse. Deux barres obliques inverses consécutives (c'est-à-dire une barre oblique inverse suivie d'une barre oblique inverse) représentent le caractère barre oblique inverse réelle. Une barre oblique inverse échappant à tout caractère autre qu'un astérisque ou une barre oblique inverse n'est pas autorisée.
Tout sauf la correspondance fait ce que son nom indique : correspond à tout sauf à ce qui est fourni dans la règle.
Tout sauf fonctionne avec une chaîne unique et des valeurs ou des listes numériques, qui doivent contenir entièrement des chaînes ou entièrement des valeurs numériques. Il peut également être appliqué à un préfixe, un suffixe ou une correspondance égale à la casse d'une chaîne ou d'une liste de chaînes.
Unique tout sauf (chaîne, puis numérique) :
{
"detail" : {
"state" : [ { "anything-but" : "initializing" } ]
}
}
{
"detail" : {
"x-limit" : [ { "anything-but" : 123 } ]
}
}
Tout sauf liste (chaînes) :
{
"detail" : {
"state" : [ { "anything-but" : [ "stopped" , "overloaded" ] } ]
}
}
Tout sauf une liste (chiffres) :
{
"detail" : {
"x-limit" : [ { "anything-but" : [ 100 , 200 , 300 ] } ]
}
}
Tout sauf le préfixe :
{
"detail" : {
"state" : [ { "anything-but" : { "prefix" : "init" } } ]
}
}
Liste tout sauf des préfixes (chaînes) :
{
"detail" : {
"state" : [ { "anything-but" : { "prefix" : [ "init" , "error" ] } } ]
}
}
Tout sauf le suffixe :
{
"detail" : {
"instance-id" : [ { "anything-but" : { "suffix" : "1234" } } ]
}
}
Liste tout sauf suffixes (chaînes) :
{
"detail" : {
"instance-id" : [ { "anything-but" : { "suffix" : [ "1234" , "6789" ] } } ]
}
}
Tout sauf ignorer la casse :
{
"detail" : {
"state" : [ { "anything-but" : { "equals-ignore-case" : "Stopped" } } ]
}
}
Liste tout sauf ignorer la casse (chaînes) :
{
"detail" : {
"state" : [ { "anything-but" : { "equals-ignore-case" : [ "Stopped" , "OverLoaded" ] } } ]
}
}
Tout sauf un caractère générique :
{
"detail" : {
"state" : [ { "anything-but" : { "wildcard" : "*/bin/*.jar" } } ]
}
}
Liste tout sauf des caractères génériques (chaînes) :
{
"detail" : {
"state" : [ { "anything-but" : { "wildcard" : [ "*/bin/*.jar" , "*/bin/*.class" ] } } ]
}
}
{
"detail" : {
"c-count" : [ { "numeric" : [ ">" , 0 , "<=" , 5 ] } ] ,
"d-count" : [ { "numeric" : [ "<" , 10 ] } ] ,
"x-limit" : [ { "numeric" : [ "=" , 3.018e2 ] } ]
}
}
Ci-dessus, les références à c-count
, d-count
et x-limit
illustrent la correspondance numérique et fonctionnent uniquement avec des valeurs qui sont des nombres JSON. La correspondance numérique prend en charge la même précision et la même plage que double
primitive de Java qui implémente la norme binary64
IEEE 754.
{
"detail" : {
"source-ip" : [ { "cidr" : "10.0.0.0/24" } ]
}
}
Cela fonctionne également avec les adresses IPv6.
Existe correspondant fonctionne sur la présence ou l'absence d'un champ dans l'événement JSON.
La règle ci-dessous correspondra à tout événement comportant un champ detail.c-count.
{
"detail" : {
"c-count" : [ { "exists" : true } ]
}
}
La règle ci-dessous correspondra à tout événement qui n'a pas de champ detail.c-count.
{
"detail" : {
"c-count" : [ { "exists" : false } ]
}
}
Remarque La correspondance Exists
ne fonctionne que sur les nœuds feuilles. Cela ne fonctionne pas sur les nœuds intermédiaires.
À titre d'exemple, l'exemple ci-dessus pour exists : false
correspondrait à l'événement ci-dessous :
{
"detail-type" : [ "EC2 Instance State-change Notification" ] ,
"resources" : [ "arn:aws:ec2:us-east-1:123456789012:instance/i-000000aaaaaa00000" ] ,
"detail" : {
"state" : [ "initializing" , "running" ]
}
}
mais correspondrait également à l'événement ci-dessous car c-count
n'est pas un nœud feuille :
{
"detail-type" : [ "EC2 Instance State-change Notification" ] ,
"resources" : [ "arn:aws:ec2:us-east-1:123456789012:instance/i-000000aaaaaa00000" ] ,
"detail" : {
"state" : [ "initializing" , "running" ]
"c-count" : {
"c1" : 100
}
}
}
{
"time" : [ { "prefix" : "2017-10-02" } ] ,
"detail" : {
"state" : [ { "anything-but" : "initializing" } ] ,
"c-count" : [ { "numeric" : [ ">" , 0 , "<=" , 5 ] } ] ,
"d-count" : [ { "numeric" : [ "<" , 10 ] } ] ,
"x-limit" : [ { "anything-but" : [ 100 , 200 , 300 ] } ] ,
"source-ip" : [ { "cidr" : "10.0.0.0/8" } ]
}
}
Comme le montrent les exemples ci-dessus, Ruler considère qu'une règle correspond si tous les champs nommés dans la règle correspondent, et il considère qu'un champ correspond si l'une des valeurs de champ fournies correspond, c'est-à-dire que Ruler a appliqué la logique "Et". à tous les champs par défaut sans la primitive "Et" est requise .
Il existe deux manières d'accéder aux effets « Ou » :
La primitive « $ou » pour permettre au client de décrire directement la relation « Ou » entre les champs de la règle.
La règle reconnaît la relation « Ou » uniquement lorsque la règle remplit toutes les conditions ci-dessous :
/src/main/software/amazon/event/ruler/Constants.java#L38
par exemple, la règle ci-dessous ne sera pas analysée comme " Ou" relation car "numérique" et "préfixe" sont des mots-clés réservés par la règle. {
"$or": [ {"numeric" : 123}, {"prefix": "abc"} ]
}
Sinon, Ruler traite simplement le "$or" comme un nom de fichier normal de la même manière que les autres chaînes de la règle.
"Ou" normal :
// Effect of "source" && ("metricName" || "namespace")
{
"source" : [ "aws.cloudwatch" ] ,
"$or" : [
{ "metricName" : [ "CPUUtilization" , "ReadLatency" ] } ,
{ "namespace" : [ "AWS/EC2" , "AWS/ES" ] }
]
}
"Ou" parallèle :
// Effect of ("metricName" || "namespace") && ("detail.source" || "detail.detail-type")
{
"$or" : [
{ "metricName" : [ "CPUUtilization" , "ReadLatency" ] } ,
{ "namespace" : [ "AWS/EC2" , "AWS/ES" ] }
] ,
"detail" : {
"$or" : [
{ "source" : [ "aws.cloudwatch" ] } ,
{ "detail-type" : [ "CloudWatch Alarm State Change" ] }
]
}
}
"Ou" contient un "Et" à l'intérieur
// Effect of ("source" && ("metricName" || ("metricType && "namespace") || "scope"))
{
"source" : [ "aws.cloudwatch" ] ,
"$or" : [
{ "metricName" : [ "CPUUtilization" , "ReadLatency" ] } ,
{
"metricType" : [ "MetricType" ] ,
"namespace" : [ "AWS/EC2" , "AWS/ES" ]
} ,
{ "scope" : [ "Service" ] }
]
}
"Ou" et "Et" imbriqués
// Effect of ("source" && ("metricName" || ("metricType && "namespace" && ("metricId" || "spaceId")) || "scope"))
{
"source" : [ "aws.cloudwatch" ] ,
"$or" : [
{ "metricName" : [ "CPUUtilization" , "ReadLatency" ] } ,
{
"metricType" : [ "MetricType" ] ,
"namespace" : [ "AWS/EC2" , "AWS/ES" ] ,
"$or" : [
{ "metricId" : [ 1234 ] } ,
{ "spaceId" : [ 1000 ] }
]
} ,
{ "scope" : [ "Service" ] }
]
}
"$or" est peut-être déjà utilisé comme clé normale dans certaines applications (bien que ce soit probablement rare). Dans ces cas, Ruler fait de son mieux pour maintenir la compatibilité ascendante. Ce n'est que lorsque les 3 conditions mentionnées ci-dessus que la règle changera de comportement, car elle suppose que votre règle voulait vraiment un OU et qu'elle a été mal configurée jusqu'à aujourd'hui. Par exemple, la règle ci-dessous continuera à fonctionner comme une règle normale en traitant « $or » comme nom de champ normal dans la règle et l'événement :
{
"source" : [ "aws.cloudwatch" ] ,
"$or" : {
"metricType" : [ "MetricType" ] ,
"namespace" : [ "AWS/EC2" , "AWS/ES" ]
}
}
Reportez-vous à /src/test/data/normalRulesWithOrWording.json
pour plus d'exemples montrant que « $or » est analysé comme nom de champ normal par Ruler.
Le mot-clé « $or » en tant que primitive de relation « Ou » ne doit pas être conçu comme un champ normal à la fois dans les événements et dans les règles. Ruler prend en charge les règles héritées où « $or » est analysé comme nom de champ normal pour maintenir la compatibilité ascendante et donner le temps à l'équipe de migrer son utilisation héritée de « $or » loin de ses événements et règles en tant que nom de fichier normal. L'utilisation mixte de "$or" comme primitive "Or" et de "$or" comme nom de champ normal n'est pas prise en charge intentionnellement par Ruler pour éviter que des ambiguïtés très gênantes sur "$or" ne se produisent.
Il existe deux manières d'utiliser Ruler. Vous pouvez compiler plusieurs règles dans une « Machine », puis utiliser l'une de ses méthodes rulesForEvent()
ou rulesForJSONEvent()
pour vérifier laquelle des règles correspond à un événement. La différence entre ces deux méthodes est discutée ci-dessous. Cette discussion utilisera rulesForEvent()
de manière générique, sauf lorsque la différence compte.
Vous pouvez également utiliser une seule méthode booléenne statique pour déterminer si un événement individuel correspond à une règle particulière.
Il existe une seule méthode booléenne statique Ruler.matchesRule(event, rule)
- les deux arguments sont fournis sous forme de chaînes JSON.
REMARQUE : Il existe une autre méthode obsolète appelée Ruler.matches(event, rule)
qui ne doit pas être utilisée car ses résultats sont incohérents avec rulesForJSONEvent()
et rulesForEvent()
. Consultez la documentation sur Ruler.matches(event, rule)
pour plus de détails.
Le temps de correspondance ne dépend pas du nombre de règles. C'est le meilleur choix si vous souhaitez sélectionner plusieurs règles possibles, et surtout si vous disposez d'un moyen de stocker la machine compilée.
Le temps de correspondance est affecté par le degré de non-déterminisme provoqué par les règles génériques et tout sauf génériques. Les performances se détériorent à mesure qu'un nombre croissant de préfixes de règles génériques correspondent à un événement théorique du pire cas. Pour éviter cela, les règles génériques relatives au même champ d'événement doivent éviter les préfixes courants menant à leur premier caractère générique. Si un préfixe commun est requis, utilisez le nombre minimum de caractères génériques et limitez les séquences de caractères répétitives qui se produisent après un caractère générique. MachineComplexityEvaluator peut être utilisé pour évaluer une machine et déterminer le degré de non-déterminisme, ou de « complexité » (c'est-à-dire combien de préfixes de règles génériques correspondent à un événement théorique du pire des cas). Voici quelques points de données montrant une diminution typique des performances pour des scores de complexité croissants.
Il est important de limiter la complexité des machines pour protéger votre application. Il existe au moins deux stratégies différentes pour limiter la complexité des machines. Celui qui a le plus de sens peut dépendre de votre application.
La stratégie n°1 est plus idéale dans la mesure où elle mesure la complexité réelle de la machine contenant toutes les règles. Lorsque cela est possible, cette stratégie doit être utilisée. L'inconvénient est que, disons que vous disposez d'un plan de contrôle qui permet la création d'une règle à la fois, jusqu'à un très grand nombre. Ensuite pour chacune de ces opérations du plan de contrôle, vous devez charger toutes les règles existantes pour effectuer la validation. Cela pourrait coûter très cher. Il est également sujet aux conditions de concurrence. La stratégie n°2 est un compromis. Le seuil utilisé par la stratégie n°2 sera inférieur à celui de la stratégie n°1 puisqu'il s'agit d'un seuil par règle. Supposons que vous souhaitiez que la complexité d'une machine, avec toutes les règles ajoutées, ne dépasse pas 300. Ensuite, avec la stratégie n°2, par exemple, vous pourriez limiter chaque machine à règle unique à une complexité de 10 et autoriser 30 règles contenant des modèles génériques. . Dans le pire des cas où la complexité est parfaitement additive (peu probable), cela conduirait à une machine d'une complexité de 300. L'inconvénient est qu'il est peu probable que la complexité soit parfaitement additive, et donc le nombre de règles contenant des caractères génériques augmentera. sera probablement limitée inutilement.
Pour la stratégie n°2, selon la manière dont les règles sont stockées, un attribut supplémentaire peut devoir être ajouté aux règles pour indiquer lesquelles sont non déterministes (c'est-à-dire contiennent des modèles de caractères génériques) afin de limiter le nombre de règles contenant des caractères génériques.
Ce qui suit est un extrait de code illustrant comment limiter la complexité d'un modèle donné, comme pour la stratégie n°2.
public class Validate { private void validate ( String pattern , MachineComplexityEvaluator machineComplexityEvaluator ) { // If we cannot compile, then return exception. List < Map < String , List < Patterns >>> compilationResult = Lists . newArrayList (); try { compilationResult . addAll ( JsonRuleCompiler . compile ( pattern )); } catch ( Exception e ) { InvalidPatternException internalException = EXCEPTION_FACTORY . invalidPatternException ( e . getLocalizedMessage ()); throw ExceptionMapper . mapToModeledException ( internalException ); } // Validate wildcard patterns. Look for wildcard patterns out of all patterns that have been used. Machine machine = new Machine (); int i = 0 ; for ( Map < String , List < Patterns >> rule : compilationResult ) { if ( containsWildcard ( rule )) { // Add rule to machine for complexity evaluation. machine . addPatternRule ( Integer . toString (++ i ), rule ); } } // Machine has all rules containing wildcard match types. See if the complexity is under the limit. int complexity = machine . evaluateComplexity ( machineComplexityEvaluator ); if ( complexity > MAX_MACHINE_COMPLEXITY ) { InvalidPatternException internalException = EXCEPTION_FACTORY . invalidPatternException ( "Rule is too complex" ); throw ExceptionMapper . mapToModeledException ( internalException ); } } private boolean containsWildcard ( Map < String , List < Patterns >> rule ) { for ( List < Patterns > fieldPatterns : rule . values ()) { for ( Patterns fieldPattern : fieldPatterns ) { if ( fieldPattern . type () == WILDCARD || fieldPattern . type () == ANYTHING_BUT_WILDCARD ) { return true ; } } } return false ; } }
La classe principale avec laquelle vous interagirez implémente la correspondance de règles basée sur la machine à états. Les méthodes intéressantes sont :
addRule()
- ajoute une nouvelle règle à la machinedeleteRule()
- supprime une règle de la machinerulesForEvent()
/ rulesForJSONEvent()
- recherche les règles de la machine qui correspondent à un événement Il existe deux versions : Machine
et GenericMachine
. Machine est simplement GenericMachine
. L'API fait référence au type générique sous le nom de « nom », ce qui reflète l'historique : la version String a été construite en premier et les chaînes qu'elle stockait et renvoyait étaient considérées comme des noms de règles.
Pour des raisons de sécurité, le type utilisé pour « nommer » les règles doit être immuable. Si vous modifiez le contenu d'un objet alors qu'il est utilisé comme nom de règle, cela peut interrompre le fonctionnement de Ruler.
Les constructeurs GenericMachine et Machine acceptent éventuellement un objet GenericMachineConfiguration, qui expose les options de configuration suivantes.
Par défaut : false Normalement, les NameStates sont réutilisés pour une sous-séquence de clé et un modèle donnés si cette sous-séquence de clé et ce modèle ont été précédemment ajoutés, ou si un modèle a déjà été ajouté pour la sous-séquence de clé donnée. Par conséquent, par défaut, la réutilisation de NameState est opportuniste. Mais en définissant cet indicateur sur true, la réutilisation de NameState sera forcée pour une sous-séquence de clé. Cela signifie que le premier modèle ajouté pour une sous-séquence de clé réutilisera un NameState si cette sous-séquence de clé a déjà été ajoutée. Cela signifie que chaque sous-séquence de clé a un seul NameState. Cela améliore l'utilisation de la mémoire de manière exponentielle dans certains cas, mais conduit à stocker davantage de sous-règles dans des NameStates individuels, sur lesquels Ruler itère parfois, ce qui peut entraîner une modeste régression des performances d'exécution. La valeur par défaut est false pour des raisons de compatibilité ascendante, mais il est probable que toutes les applications, à l'exception des applications les plus sensibles à la latence, gagneraient à définir cette valeur sur true.
Voici un exemple simple. Considérer:
machine . addRule ( "0" , "{"key1": ["a", "b", "c"]}" ) ;
Le modèle "a" crée un NameState, puis, même avec additionNameStateReuse=false, le deuxième modèle ("b") et le troisième modèle ("c") réutilisent ce même NameState. Mais considérez plutôt ce qui suit :
machine . addRule ( "0" , "{"key1": ["a"]}" ) ;
machine . addRule ( "1" , "{"key1": ["b"]}" ) ;
machine . addRule ( "2" , "{"key1": ["c"]}" ) ;
Maintenant, avec additionnelNameStateReuse=false, nous nous retrouvons avec trois NameStates, car le premier modèle rencontré pour une sous-séquence de clé à chaque ajout de règle créera un nouveau NameState. Ainsi, "a", "b" et "c" ont tous leurs propres NameStates. Cependant, avec additionnelNameStateReuse=true, "a" créera un nouveau NameState, puis "b" et "c" réutiliseront ce même NameState. Ceci est accompli en stockant le fait que nous avons déjà un NameState pour la sous-séquence de clé « key1 ».
Notez que peu importe si chaque addRule utilise un nom de règle différent ou le même nom de règle.
Toutes les formes de cette méthode ont le même premier argument, une chaîne qui fournit le nom de la règle et est renvoyée par rulesForEvent()
. Le reste des arguments fournit les paires nom/valeur. Ils peuvent être fournis en JSON comme dans les exemples ci-dessus (via un String, un Reader, un InputStream ou byte[]
), ou sous forme de Map
, où les clés sont les noms de champs et les les valeurs sont la liste des correspondances possibles ; en utilisant l'exemple ci-dessus, il y aurait une clé nommée detail.state
dont la valeur serait la liste contenant "initializing"
et "running"
.
Remarque : Cette méthode (et également deleteRule()
) est synchronisée, donc un seul thread peut mettre à jour la machine à tout moment.
Vous pouvez appeler addRule()
plusieurs fois avec le même nom mais avec plusieurs modèles nom/valeur différents, obtenant ainsi une relation « ou » ; rulesForEvent()
renverra ce nom si l'un des modèles correspond.
Par exemple, supposons que vous appeliez addRule()
avec le nom de règle « R1 » et que vous ajoutiez le modèle suivant :
{
"detail" : {
"c-count" : [ { "numeric" : [ ">" , 0 , "<=" , 5 ] } ]
}
}
Ensuite, vous l'appelez à nouveau avec le même nom mais avec un modèle différent :
{
"detail" : {
"x-limit" : [ { "numeric" : [ "=" , 3.018e2 ] } ]
}
}
Après cela, rulesForEvent()
renverra "R1" pour une valeur c-count
de 2 ou une valeur x-limit
de 301,8.
Ceci est une image miroir de addRule()
; dans chaque cas, le premier argument est le nom de la règle, donné sous forme de chaîne. Les arguments suivants fournissent les noms et les valeurs, et peuvent être donnés de la même manière qu'avec addRule()
.
Remarque : Cette méthode (et également addRule()
) est synchronisée, donc un seul thread peut mettre à jour la machine à tout moment.
Le fonctionnement de cette API peut être subtil. La Machine compile le mappage des modèles de nom/valeur aux noms de règles dans un automate fini, mais ne se souvient pas des modèles mappés à un nom de règle donné. Ainsi, il n’est pas nécessaire que le modèle d’un deleteRule()
corresponde exactement à celui du addRule()
correspondant. Ruler recherchera les correspondances avec les modèles nom/valeur et verra s'ils donnent une correspondance à une règle portant le nom fourni, et si c'est le cas, les supprimera. Gardez à l'esprit que même si l'exécution d'appels deleteRule()
qui ne correspondent pas exactement aux appels addRule()
correspondants n'échouera pas et ne laissera pas la machine dans un état incohérent, ils peuvent provoquer l'accumulation de « déchets » dans la machine.
Une conséquence spécifique est que si vous avez appelé addRule()
plusieurs fois avec le même nom mais des modèles différents, comme illustré ci-dessus dans la section Règles et noms de règles , vous devrez appeler deleteRule()
le même nombre de fois, avec le même modèles associés, pour supprimer toutes les références à ce nom de règle de la machine.
Cette méthode renvoie un List
pour Machine (et List
pour GenericMachine) qui contient les noms des règles qui correspondent à l'événement fourni. L'événement peut être fourni à l'une ou l'autre méthode sous la forme d'une String
unique représentant sa forme JSON.
L'événement peut également être fourni à rulesForEvent()
sous la forme d'une collection de chaînes qui alternent les noms et valeurs de champ, et doivent être triées lexicalement par nom de champ. Il peut s'agir d'un List
ou String[]
.
Fournir l'événement au format JSON est l'approche recommandée et présente plusieurs avantages. Tout d'abord, remplir la liste ou le tableau de chaînes avec des quantités alternées de nom/valeur, dans un ordre trié par nom, est délicat, et Ruler n'aide pas, ne fonctionne tout simplement pas correctement si la liste est mal structurée. Pour ajouter à la difficulté, la représentation des valeurs de champ, fournies sous forme de chaînes, doit suivre les règles de syntaxe JSON - voir ci-dessous sous Correspondance de texte JSON .
Enfin, la version liste/tableau d'un événement empêche Ruler de reconnaître les structures de tableau et de fournir une correspondance cohérente avec le tableau, décrite ci-dessous dans ce document. L'API rulesForEvent(String eventJSON)
est obsolète au profit de rulesForJSONEvent()
spécifiquement parce qu'elle ne prend pas en charge la correspondance cohérente avec les tableaux.
rulesForJSONEvent()
présente également l'avantage que le code qui transforme la forme JSON de l'événement en une liste triée a été largement profilé et optimisé.
Les performances de rulesForEvent()
et rulesForJSONEvent()
ne dépendent pas du nombre de règles ajoutées avec addRule()
. rulesForJSONEvent()
est généralement plus rapide en raison du traitement optimisé des événements. Si vous effectuez votre propre traitement d'événements et appelez rulesForEvent()
avec une liste pré-triée de noms et de valeurs, c'est encore plus rapide ; mais vous ne pourrez peut-être pas préparer la liste de champs aussi rapidement que le fait rulesForJSONEvent()
.
Cette méthode correspond approximativement au nombre d'objets dans la machine. Sa valeur ne varie qu'à mesure que des règles sont ajoutées ou supprimées. Ceci est utile pour identifier les grosses machines qui nécessitent potentiellement beaucoup de mémoire. Comme cette méthode dépend du nombre d'objets internes, ce nombre peut changer lorsque les éléments internes de la bibliothèque de règles sont modifiés. La méthode effectue tous ses calculs au moment de l'exécution pour éviter de consommer de la mémoire et d'aggraver l'impact des grandes machines à règles. Son calcul n'est intentionnellement PAS thread-safe pour éviter de bloquer les évaluations de règles et les modifications de la machine. Cela signifie que si un processus parallèle ajoute ou supprime de la machine, vous pouvez obtenir des résultats différents par rapport à la fin de ces processus parallèles. De plus, comme la bibliothèque optimise ses éléments internes pour certains modèles (voir ShortcutTransition.java
pour plus de détails), vous pouvez également obtenir des résultats différents en fonction de l'ordre dans lequel les règles ont été ajoutées ou supprimées.
Si vous considérez vos événements comme des paires nom/valeur plutôt que comme des documents imbriqués de style JSON, la classe Patterns
(et sa sous-classe Range
) peuvent être utiles pour construire des règles. Les méthodes statiques suivantes sont utiles.
public static ValuePatterns exactMatch ( final String value );
public static ValuePatterns prefixMatch ( final String prefix );
public static ValuePatterns prefixEqualsIgnoreCaseMatch ( final String prefix );
public static ValuePatterns suffixMatch ( final String suffix );
public static ValuePatterns suffixEqualsIgnoreCaseMatch ( final String suffix );
public static ValuePatterns equalsIgnoreCaseMatch ( final String value );
public static ValuePatterns wildcardMatch ( final String value );
public static AnythingBut anythingButMatch ( final String anythingBut );
public static AnythingBut anythingButMatch ( final Set < String > anythingButs );
public static AnythingBut anythingButMatch ( final double anythingBut );
public static AnythingBut anythingButNumberMatch ( final Set < Double > anythingButs );
public static AnythingButValuesSet anythingButPrefix ( final String prefix );
public static AnythingButValuesSet anythingButPrefix ( final Set < String > anythingButs );
public static AnythingButValuesSet anythingButSuffix ( final String suffix );
public static AnythingButValuesSet anythingButSuffix ( final Set < String > anythingButs );
public static AnythingButValuesSet anythingButIgnoreCaseMatch ( final String anythingBut );
public static AnythingButValuesSet anythingButIgnoreCaseMatch ( final Set < String > anythingButs );
public static AnythingButValuesSet anythingButWildcard ( final String value );
public static AnythingButValuesSet anythingButWildcard ( final Set < String > anythingButs );
public static ValuePatterns numericEquals ( final double val );
public static Range lessThan ( final double val );
public static Range lessThanOrEqualTo ( final double val );
public static Range greaterThan ( final double val );
public static Range greaterThanOrEqualTo ( final double val );
public static Range between ( final double bottom , final boolean openBottom , final double top , final boolean openTop );
Une fois que vous avez construit Patterns
de correspondance appropriés avec ces méthodes, vous pouvez utiliser les méthodes suivantes pour ajouter ou supprimer de votre machine :
public void addPatternRule ( final String name , final Map < String , List < Patterns >> namevals );
public void deletePatternRule ( final String name , final Map < String , List < Patterns >> namevals );
REMARQUE : les mises en garde répertoriées dans deleteRule()
s'appliquent également à deletePatternRule()
.
Les valeurs de champ dans les règles doivent être fournies dans leurs représentations JSON. Autrement dit, les valeurs de chaîne doivent être placées entre « guillemets ». Les valeurs sans guillemets sont autorisées, telles que les nombres ( -3.0e5
) et certains littéraux spécifiques à JSON ( true
, false
et null
).
Cela peut être entièrement ignoré si des règles sont fournies à addRule()
() sous forme JSON, ou si vous travaillez avec des modèles plutôt qu'avec des chaînes littérales. Mais si vous fournissez des règles sous forme de paires nom/valeur et que vous souhaitez spécifier que le champ "xyz" correspond à la chaîne "true", celle-ci doit être exprimée sous la forme "xyz", ""true""
. D'un autre côté, "xyz", "true"
correspondrait uniquement au littéral JSON true
.
Ruler prend en charge la correspondance de règles pour les événements contenant des tableaux, mais uniquement lorsque l'événement est fourni sous forme JSON : lorsqu'il s'agit d'une liste de champs pré-triés, la structure du tableau de l'événement est perdue. Le comportement dépend également de si vous utilisez rulesForEvent()
ou rulesForJSONEvent
.
Considérez l'événement suivant.
{
"employees" : [
{ "firstName" : "John" , "lastName" : "Doe" } ,
{ "firstName" : "Anna" , "lastName" : "Smith" } ,
{ "firstName" : "Peter" , "lastName" : "Jones" }
]
}
Alors cette règle correspondra :
{ "employees" : { "firstName" : [ "Anna" ] } }
Autrement dit, la structure du tableau est « supprimée » du modèle de règle et tous les objets contenus sont traités comme s'ils constituaient la valeur du champ parent. Cela fonctionne également pour les tableaux à plusieurs niveaux :
{
"employees" : [
[
{ "firstName" : "John" , "lastName" : "Doe" } ,
{ "firstName" : "Anna" , "lastName" : "Smith" }
] ,
[
{ "firstName" : "Peter" , "lastName" : "Jones" }
]
]
}
Dans les versions antérieures de Ruler, la seule méthode de correspondance basée sur la machine était rulesForEvent()
qui malheureusement correspondra également à la règle suivante :
{ "employees" : { "firstName" : [ "Anna" ] , "lastName" : [ "Jones" ] } }
En guise de correctif, Ruler a introduit rulesForJSONEvent()
qui, comme son nom l'indique, ne correspond qu'aux événements fournis sous forme JSON. rulesForJsonEvent()
ne correspondra pas à la règle "Anna"/"Jones" ci-dessus.
Formellement : rulesForJSONEvent()
refusera de reconnaître toute correspondance dans laquelle deux champs quelconques se trouvent dans des objets JSON qui se trouvent dans des éléments différents du même tableau. En pratique, cela signifie qu’il fait à peu près ce à quoi vous vous attendez.
Il existe une classe de support com.amazon.fsm.ruler.RuleCompiler
. Il contient une méthode nommée check()
qui accepte une définition de règle JSON et renvoie une valeur String qui, si nulle, signifie que la règle était syntaxiquement valide. Si la valeur de retour n'est pas Null, elle contient un message d'erreur lisible par l'homme décrivant le problème.
Pour plus de commodité, il contient également une méthode nommée compile()
qui fonctionne comme check()
mais signale une erreur en lançant une IOException et, en cas de succès, renvoie un Map
sous la forme addRule()
de Machine. addRule()
méthode attend. Étant donné que la classe Machine l’utilise en interne, cette méthode peut vous faire gagner du temps.
Lorsque Ruler compile des clés, il utilise le point ( .
) comme caractère de jointure. Cela signifie qu'il compilera les deux règles suivantes dans la même représentation interne
## has no dots in keys
{ "detail" : { "state" : { "status" : [ "running" ] } } }
## has dots in keys
{ "detail" : { "state.status" : [ "running" ] } }
Cela signifie également que ces règles s'affronteront contre les deux événements suivants :
## has no dots in keys
{ "detail" : { "state" : { "status" : "running" } } }
## has dots in keys
{ "detail" : { "state.status" : "running" } }
Ce comportement peut changer dans les versions futures (pour éviter toute confusion) et ne doit pas être invoqué.
Nous mesurons les performances de Ruler en compilant plusieurs règles dans une machine et en faisant correspondre les événements fournis sous forme de chaînes JSON.
Un benchmark qui traite 213 068 événements JSON avec une taille moyenne d'environ 900 octets contre 5 pour chaque correspondance exacte, correspondance de préfixe, correspondance de suffixe, correspondance égale à ignorer la casse, correspondance par caractère générique, correspondance numérique et tout sauf correspondance. règles et compte les matchs, donne ce qui suit sur un MacBook 2019 :
Les événements sont traités à plus de 220 000/seconde, à l'exception de :
Voici quelques suggestions sur les règles de traitement et les événements :
Du point de vue des performances, Ruler est sensible aux éléments ci-dessous. Ainsi, lorsque vous concevez le schéma de votre événement et de votre règle, voici quelques suggestions :
Voir CONTRIBUTION pour plus d'informations.
Ce projet est sous licence Apache-2.0. Voir LICENCE pour plus d’informations.