37 votes

ServiceStack: gestion des versions de ressources RESTful

J'ai pris un lire pour les Avantages de message les services web basés sur l'article et je me demande si il n'y est-il recommandé de style/de la pratique à la gestion des versions Reposant ressources en ServiceStack? Les différentes versions peuvent rendre des réponses différentes ou ont des différents paramètres d'entrée dans la Demande de DTO.

Je suis en se penchant vers une URL du type de contrôle de version (j'.e /v1/films/{Id}), mais j'ai vu d'autres pratiques de la version dans les en-têtes HTTP (j'.e Content-Type: application/vnd.de l'entreprise.myapp-v2).

Je suis l'espoir d'une manière qui fonctionne avec les métadonnées de la page, mais pas tellement une exigence que j'ai remarqué tout simplement à l'aide de la structure de dossier/ namespacing fonctionne très bien lors du rendu des routes.

Par exemple (ce qui ne rend pas droit dans les métadonnées de la page, mais effectue correctement si vous connaissez la route directe/url)

  • /v1/films/{id}
  • /v1.1/les films/{id}

Code

namespace Samples.Movies.Operations.v1_1
{
    [Route("/v1.1/Movies", "GET")]
    public class Movies
    {
       ...
    } 
}
namespace Samples.Movies.Operations.v1
{
    [Route("/v1/Movies", "GET")]
    public class Movies
    {
       ...
    }   
}

et des services correspondants...

public class MovieService: ServiceBase<Samples.Movies.Operations.v1.Movies>
{
    protected override object Run(Samples.Movies.Operations.v1.Movies request)
    {
    ...
    }
}

public class MovieService: ServiceBase<Samples.Movies.Operations.v1_1.Movies>
    {
        protected override object Run(Samples.Movies.Operations.v1_1.Movies request)
        {
        ...
        }
    }

60voto

mythz Points 54874

Essayer de faire évoluer (pas de ré-implémenter) les services existants

Pour la gestion des versions, vous allez être dans un monde de mal si vous essayez de maintenir différents types statiques pour les différentes version d'extrémité. Nous avons d'abord commencé en bas de cette route, mais dès que vous commencez à l'appui de votre première version de l'effort de développement afin de maintenir de multiples versions d'un même service explose car vous en aurez besoin pour maintenir manuel de la cartographie des différents types de facilement les fuites en avoir plusieurs implémentations parallèles, chacun couplé à une les différentes versions de type une violation massive de la SEC. C'est moins un problème pour les langages dynamiques où les mêmes modèles peuvent facilement être ré-utilisés par les différentes versions.

Profitez de gestion des versions dans sérialiseurs

Ma recommandation est de ne pas explicitement version, mais de tirer avantage de la gestion des versions des capacités à l'intérieur de l'formats de sérialisation.

E. g: vous n'avez généralement pas besoin de s'inquiéter à propos du contrôle de version avec JSON clients comme la gestion des versions des capacités de l' JSON et JSV Sérialiseurs sont bien plus résistants.

Améliorer vos services existants sur la défensive

Avec XML et DataContract vous pouvez facilement ajouter et supprimer des champs sans faire une modification de rupture. Si vous ajoutez IExtensibleDataObject pour votre réponse DTO est vous avez également un potentiel pour accéder à des données qui n'est pas défini sur la DTO. Mon approche de la gestion des versions est au programme, sur la défensive, afin de ne pas introduire une modification de rupture, vous pouvez vérifier que c'est le cas avec les tests d'Intégration à l'aide de vieux Otd. Voici quelques conseils que j'ai suivi:

  • Ne pas changer le type d'une propriété existante - Si vous avez besoin d'un autre type d'ajouter un autre à la propriété et à l'utilisation de l'ancien/existante pour déterminer la version
  • Programme défensivement réaliser ce que les propriétés n'existent pas avec les anciens clients afin de ne pas la rendre obligatoire.
  • Garder un seul espace de noms global (uniquement pour les XML/SOAP extrémités)

- Je le faire en utilisant le [assemblée] attribut dans la AssemblyInfo.cs de chacun de vos DTO projets:

[assembly: ContractNamespace("http://schemas.servicestack.net/types", 
    ClrNamespace = "MyServiceModel.DtoTypes")]

L'assemblée attribut vous permet d'économiser de la main à la spécification des espaces de noms explicites sur chaque DTO, j'.e:

namespace MyServiceModel.DtoTypes {
    [DataContract(Namespace="http://schemas.servicestack.net/types")]
    public class Foo { .. }
}

Si vous souhaitez utiliser un autre espace de noms XML de la valeur par défaut ci-dessus, vous devez l'enregistrer auprès de:

SetConfig(new EndpointHostConfig {
    WsdlServiceNamespace = "http://schemas.my.org/types"
});

L'incorporation de gestion des versions dans les Dto

La plupart du temps, si vous programmez sur la défensive et de faire évoluer vos services normalement vous n'aurez pas besoin de savoir exactement quelle est la version d'un client spécifique est de l'aide que vous pouvez déduire à partir des données qui est rempli. Mais dans les rares cas de vos besoins en matière de services de tordre le comportement basé sur la version spécifique du client, vous pouvez incorporer des informations de version dans votre Otd.

Avec la première version de votre Otd vous publiez, vous pouvez heureusement les créer sans aucune pensée de versioning.

class Foo {
  string Name;
}

Mais peut-être que pour une raison quelconque, la Forme/UI a été changé, et vous ne vouliez plus le Client à utiliser le ambiguë Nom de la variable et vous aussi voulu suivre la version spécifique du client à l'aide de:

class Foo {
  Foo() {
     Version = 1;
  }
  int Version;
  string Name;
  string DisplayName;
  int Age;
}

Plus tard, il a été discuté dans une réunion d'Équipe, DisplayName n'était pas assez bon et vous devez les diviser en différents domaines:

class Foo {
  Foo() {
     Version = 2;
  }
  int Version;
  string Name;
  string DisplayName;
  string FirstName;
  string LastName;  
  DateTime? DateOfBirth;
}

Donc l'état actuel, c'est que vous avez 3 différentes versions de client, avec les appels existants qui ressemblent à:

la Version 1:

client.Post(new Foo { Name = "Foo Bar" });

sortie de la v2:

client.Post(new Foo { Name="Bar", DisplayName="Foo Bar", Age=18 });

v3 Version:

client.Post(new Foo { FirstName = "Foo", LastName = "Bar", 
   DateOfBirth = new DateTime(1994, 01, 01) });

Vous pouvez continuer à gérer ces différentes versions de la même application (qui sera à l'aide de la dernière v3 version de l'Otd) e.g:

class FooService : Service {

    public object Post(Foo request) {
        //v1: 
        request.Version == 0 
        request.Name == "Foo"
        request.DisplayName == null
        request.Age = 0
        request.DateOfBirth = null

        //v2:
        request.Version == 2
        request.Name == null
        request.DisplayName == "Foo Bar"
        request.Age = 18
        request.DateOfBirth = null

        //v3:
        request.Version == 3
        request.Name == null
        request.DisplayName == null
        request.FirstName == "Foo"
        request.LastName == "Bar"
        request.Age = 0
        request.DateOfBirth = new DateTime(1994, 01, 01)
    }
}

2voto

No One Points 19

Cerner le Problème

L'API est la partie de votre système qui expose son expression. Il définit les concepts et la sémantique de la communication dans votre domaine. Le problème vient quand vous voulez changer ce qui peut être exprimé ou comment il peut être exprimé.

Il peut y avoir des différences dans la méthode d'expression et ce qui est exprimé. Le premier problème a tendance à être des différences dans les jetons (nom et prénom au lieu du nom). Le deuxième problème est d'exprimer différentes choses (la possibilité de renommer soi-même).

À long terme gestion des versions de la solution aurez besoin pour résoudre ces problèmes.

L'évolution d'une API

L'évolution d'un service en changeant les types de ressources est un type de l'implicite de la gestion des versions. Il utilise la construction de l'objet pour déterminer le comportement. Sa fonctionne mieux lorsqu'il y a seulement des modifications mineures à la méthode d'expression (comme les noms). Il ne fonctionne pas bien pour les plus complexes des modifications à la méthode d'expression ou des changements à la modification de l'expressivité. Code tend à se disperser à travers.

Spécifique De Gestion Des Versions

Lorsque les changements seront plus complexes, il est important de garder la logique pour chaque version séparée. Même dans mythz exemple, il a séparé les code pour chaque version. Toutefois, le code est toujours mélangés dans les mêmes méthodes. Il est très facile pour code pour les différentes versions de commencer à s'effondrer les uns sur les autres et il est susceptible de se disperser. Se débarrasser de support pour une version précédente peut être difficile.

En outre, vous aurez besoin de garder votre ancien code de synchronisation pour tout changement dans ses dépendances. Si des modifications de base de données, le soutien des codes de l'ancien modèle seront également besoin de changer.

Une Meilleure Façon

Le meilleur moyen que j'ai trouvé est de s'attaquer à l'expression directement le problème. Chaque fois qu'une nouvelle version de l'API est disponible, il sera mis en œuvre sur le dessus de la nouvelle couche. Ceci est généralement facile car les modifications sont de petite taille.

Il brille vraiment de deux façons: d'abord tous les code pour gérer la cartographie est en un seul endroit, de sorte qu'il est facile de comprendre ou de les supprimer plus tard et deuxième il ne nécessite pas d'entretien comme de nouvelles Api sont développés (la poupée russe modèle).

Le problème, c'est quand la nouvelle API est moins expressif que l'ancienne API. C'est un problème qui devra être résolu quelle que soit la solution est de garder l'ancienne version autour. Il devient clair qu'il y a un problème et que la solution du problème.

L'exemple de mythz l'exemple dans ce style:

namespace APIv3 {
    class FooService : RestServiceBase<Foo> {
        public object OnPost(Foo request) {
            var data = repository.getData()
            request.FirstName == data.firstName
            request.LastName == data.lastName
            request.DateOfBirth = data.dateOfBirth
        }
    }
}
namespace APIv2 {
    class FooService : RestServiceBase<Foo> {
        public object OnPost(Foo request) {
            var v3Request = APIv3.FooService.OnPost(request)
            request.DisplayName == v3Request.FirstName + " " + v3Request.LastName
            request.Age = (new DateTime() - v3Request.DateOfBirth).years
        }
    }
}
namespace APIv1 {
    class FooService : RestServiceBase<Foo> {
        public object OnPost(Foo request) {
            var v2Request = APIv2.FooService.OnPost(request)
            request.Name == v2Request.DisplayName
        }
    }
}

Chaque objet exposé est clair. Le même code de mappage doit encore être rédigés dans les deux styles, mais dans la séparation de style, que la cartographie pertinente à un type qui doit être écrit. Il n'est pas nécessaire de mapper explicitement code ne s'applique pas (qui est juste une autre source potentielle d'erreur). La dépendance de la précédente Api est statique lorsque vous ajoutez des Api ou de modifier la dépendance de la couche API. Par exemple, si la source de données change, alors que la plus récente de l'API (version 3) doit changer dans ce style. Dans le style, vous auriez besoin de code les changements pour chacun des Api prises en charge.

Une préoccupation dans les commentaires était le plus de types de la base de code. Ce n'est pas un problème parce que ces types sont exposés à l'extérieur. En fournissant les types explicitement dans le code de base et les rend faciles à découvrir et à isoler dans le test. Il est beaucoup mieux pour la maintenabilité d'être clair. Un autre avantage est que cette méthode ne permet pas de produire de la logique supplémentaire, mais seulement ajoute des types additionnels.

2voto

traviskds Points 21

Je suis aussi d'essayer de trouver une solution à ce et a été la pensée de faire quelque chose comme ci-dessous. (Basé sur un grand nombre de Googlling et StackOverflow interrogation c'est donc construit sur les épaules de beaucoup d'autres.)

Tout d'abord, je ne veux pas de débat si la version devrait être dans l'URI ou en-Tête de Requête. Il y a des avantages/inconvénients des deux approches, donc je pense que chacun de nous a besoin d'utiliser ce qui répond à nos besoins.

C'est sur la façon de design/architecture Java Message d'Objets et les Ressources de mise en Œuvre de classes.

Donc, nous allons l'obtenir.

Je l'approche de ce en deux étapes. Des Modifications mineures (par exemple, 1.0 à 1.1) et les Changements Importants (e.g 1.1 à 2.0)

Approche pour les modifications mineures

Donc, disons-nous aller par le même exemple les classes utilisées par @mythz

Au départ, nous avons

class Foo {   string Name; }

Nous fournissons l'accès à cette ressource comme /V1.0/fooresource/{id}

Dans mon cas d'utilisation, je l'utilise de JAX-RS,

@Path("/{versionid}/fooresource")
public class FooResource {

    @GET
    @Path( "/{id}" )
    public Foo getFoo (@PathParam("versionid") String versionid, (@PathParam("id") String fooId) 
    {
      Foo foo = new Foo();
     //setters, load data from persistence, handle business logic etc                   
     Return foo;
    }
}

Maintenant, nous allons dire que nous en ajouter 2 autres propriétés à Toto.

class Foo { 
    string Name;   
    string DisplayName;   
    int Age; 
}

Ce que je fais à ce point est d'annoter les propriétés avec un @Version annotation

class Foo { 
    @Version("V1.0")string Name;   
    @Version("V1.1")string DisplayName;   
    @Version("V1.1")int Age; 
}

Ensuite, j'ai un filtre à réponse qui sera basé sur la version demandée, retourner à l'utilisateur uniquement les propriétés qui correspondent à cette version. Notez que pour plus de commodité, si il y a des propriétés qui doivent être retournés pour toutes les versions, alors vous n'avez pas l'annoter et le filtre sera de retour ce quelle que soit la version demandée

C'est un peu comme une couche de médiation. Ce que j'ai expliqué, c'est une version simpliste et il peut devenir très compliqué, mais j'espère que vous obtenez l'idée.

Approche pour la Version Majeure

Maintenant, cela peut être assez compliqué quand il y a beaucoup de changements ont été fait à partir d'une version à l'autre. C'est alors que nous avons besoin de se déplacer à la 2e option.

L'Option 2 est essentiellement la branche de la base de code et ensuite faire les modifications sur la base de code de l'hôte et les deux versions sur différents contextes. À ce stade, nous pourrions avoir à refactoriser le code de base et un peu de supprimer la version de médiation complexité introduite dans l'Approche d'un (c'est à dire rendre le code plus propre), Cela peut être principalement dans les filtres.

Notez que c'est juste ce que je pense et n'ont pas mis en œuvre, il encore et je me demande si c'est une bonne idée.

Aussi je me demandais si il y a des bonnes médiation moteurs/ESB qui peut faire ce type de transformation, sans avoir à utiliser les filtres mais je n'ai pas vu tout c'est aussi simple que d'utiliser un filtre. Peut-être que je n'ai pas cherché assez.

Intéressés à connaître les pensées des autres et si cette solution permettra de répondre à la question d'origine.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X