Tout d'abord, la "meilleure" solution est un terme assez subjectif. Je vise généralement des solutions DRY, réutilisables et performantes, qui demandent le moins d'efforts, de frictions et de bavardages possible, tandis que d'autres définiront la "meilleure" solution en fonction de sa conformité aux principes de REST. Vous obtiendrez donc des réponses variées en fonction de vos objectifs. Je ne peux que vous proposer la façon dont j'aborderais la question.
Les implémentations de services ServiceStack sont découplées de leurs routes personnalisées.
Une chose à garder à l'esprit est que la façon dont vous définissez et concevez vos services dans ServiceStack est assez découplée de la façon dont vous les exposez, puisque vous pouvez exposer vos services sous n'importe quelle route personnalisée. ServiceStack encourage une conception basée sur les messages, vous devez donc donner à chaque opération un message distinct.
Utiliser une structure d'URL logique / hiérarchique
J'utiliserais une structure logique d'Url qui vise à représenter l'identifiant d'un nom, qui est structuré hiérarchiquement, c'est-à-dire que le chemin parent catégorise votre ressource et lui donne un contexte significatif. Ainsi, dans ce cas, si vous souhaitez exposer des événements et des critiques, je suis enclin à utiliser la structure d'URL suivante :
/events //all events
/events/1 //event #1
/events/1/reviews //event #1 reviews
Chacun de ces identificateurs de ressources peut se voir appliquer n'importe quel verbe HTTP.
Mise en œuvre
Pour la mise en œuvre, je suis généralement une conception basée sur les messages et je regroupe toutes les opérations connexes en fonction du type de réponse et du contexte d'appel. Pour cela, je ferais quelque chose comme :
[Route("/events", "GET")]
[Route("/events/category/{Category}", "GET")] //*Optional top-level views
public class Events : IReturn<EventsResponse>
{
//Optional resultset filters, e.g. ?Category=Tech&Search=servicestack
public string Category { get; set; }
public string Search { get; set; }
}
[Route("/events", "POST")]
public class CreateEvent : IReturn<Event>
{
public string Name { get; set; }
public DateTime StartDate { get; set; }
}
[Route("/events/{Id}", "GET")]
[Route("/events/code/{EventCode}", "GET")] //*Optional
public class GetEvent : IReturn<Event>
{
public int Id { get; set; }
public string EventCode { get; set; } //Alternative way to fetch an Event
}
[Route("/events/{Id}", "PUT")]
public class UpdateEvent : IReturn<Event>
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime StartDate { get; set; }
}
Et suivez un schéma similaire pour les examens d'événements
[Route("/events/{EventId}/reviews", "GET")]
public class EventReviews : IReturn<EventReviewsResponse>
{
public int EventId { get; set; }
}
[Route("/events/{EventId}/reviews/{Id}", "GET")]
public class GetEventReview : IReturn<EventReview>
{
public int EventId { get; set; }
public int Id { get; set; }
}
[Route("/events/{EventId}/reviews", "POST")]
public class CreateEventReview : IReturn<EventReview>
{
public int EventId { get; set; }
public string Comments { get; set; }
}
L'implémentation devrait être assez simple en se basant sur ces messages, que (selon la taille de la base de code) j'organiserais en 2 EventsService y EventReviewsService classes. Je dois noter que j'utilise moi-même le pluriel pour les noms de DTO de demande de service afin d'éviter tout conflit avec les modèles de données du même nom.
Bien que j'aie séparé UpdateEvent
y CreateEvent
ici, je vais parfois les fusionner en un seul idempotent StoreEvent
si le cas d'utilisation le permet.
Idéalement, le niveau racine AppHost doit rester léger et sans implémentation. Pour les petits projets ne comportant que quelques services, il est possible de tout regrouper dans un seul projet et de faire évoluer l'architecture en fonction des besoins.
Pour les projets de taille moyenne à grande, nous recommandons la structure physique ci-dessous. Pour les besoins de cet exemple, nous supposerons que notre application s'appelle EventMan .
L'ordre des projets indique également ses dépendances, par exemple, le projet de premier niveau EventMan
références du projet todo sous-projets tandis que le dernier EventMan.ServiceModel
références du projet aucun :
- EventMan
AppHost.cs // ServiceStack ASP.NET Web or Console Host Project
- EventMan.ServiceInterface // Service implementations (akin to MVC Controllers)
EventsService.cs
EventsReviewsService.cs
- EventMan.Logic //For larger projs: pure C# logic, data models, etc
IGoogleCalendarGateway //E.g of a external dependency this project could use
- EventMan.ServiceModel //Service Request/Response DTOs and DTO types
Events.cs //Events, CreateEvent, GetEvent, UpdateEvent DTOs
EventReviews.cs //EventReviews, GetEventReview, CreateEventReview
Types/
Event.cs //Event type
EventReview.cs //EventReview type
Avec le EventMan.ServiceModel
conservés dans leur propre implémentation séparée et dans une dll sans dépendance, vous pouvez librement partager cette dll dans n'importe quel projet client .NET tel quel - que vous pouvez utiliser avec n'importe quel programme générique de la gamme Clients de service C# pour fournir une API typée de bout en bout sans aucun code-gen.
Mise à jour
Cette structure de projet recommandée est maintenant contenue dans toutes les Modèles VS.NET de ServiceStackVS .