From 12ec0cc825170a7ef4b718e5c4ca77c2adaca090 Mon Sep 17 00:00:00 2001 From: Hassan Rezk Habib Date: Sun, 9 Jun 2024 11:22:43 -0700 Subject: [PATCH] DOCUMENTATIONS: Introduce Custom Http Verbs & UrlEncoded Forms --- .../3.1.1 RESTful APIs/3.1.1 RESTful APIs.md | 80 ++++++++++++++++--- 1 file changed, 71 insertions(+), 9 deletions(-) diff --git a/3. Exposers/3.1 Communication Protocols/3.1.1 RESTful APIs/3.1.1 RESTful APIs.md b/3. Exposers/3.1 Communication Protocols/3.1.1 RESTful APIs/3.1.1 RESTful APIs.md index 0d70045..e8d97fe 100644 --- a/3. Exposers/3.1 Communication Protocols/3.1.1 RESTful APIs/3.1.1 RESTful APIs.md +++ b/3. Exposers/3.1 Communication Protocols/3.1.1 RESTful APIs/3.1.1 RESTful APIs.md @@ -20,7 +20,7 @@ Several rules and principles govern the implementation of RESTful API endpoints. ### 3.1.1.2.0 Language Controllers speak a different language when it comes to implementing their methods than services and brokers. For instance, if a broker that interfaces with a storage uses a language such as `InsertStudentAsync` and its corresponding service implementation uses something like `AddStudentAsync`, the controller equivalent will use a RESTful language such as `PostStudentAsync`. -A controller would use only a handful of terminologies to express a particular operation. Let's draw the map here for clarity: +A common controller would use only a handful of terminologies to express a particular operation. Let's draw the map here for clarity: | Controllers | Services | Brokers | |------------------------ |--------------------------- | ------------------------------------------| @@ -29,15 +29,35 @@ A controller would use only a handful of terminologies to express a particular o | Put | Modify | Update | | Delete | Remove | Delete | -The language controllers speak is called HTTP Verbs. Their range is wider than the aforementioned CRUD operations. For instance, there is PATCH, which allows API consumers to update only portions of a particular document. From my experience in productionized applications, PATCH is rarely used today. However, I may specialize in them in a special section in future versions of The Standard. +But it must be important to understand that these operations can be extended and modified to fit the operation we are performing. We will talk about that shortly. +The language controllers speak is called HTTP Verbs. Their range is wider than the aforementioned CRUD operations. For instance, there is PATCH, which allows API consumers to update only portions of a particular document. From my experience in productionized applications, PATCH is rarely used today. However, I may expand on them in a special section in future versions of The Standard. #### 3.1.1.2.0.0 Beyond CRUD Routines -As we mentioned before, a controller can interface with more than just a foundation service. They can interface with higher-order business logic functions. For instance, a processing service may offer an `Upsert` routine. In this case, a typical Http Verb wouldn't be able to satisfy a combinational routine such as an `Upsert`. In this case, we resolve to the initial state of `Post`, assuming the resource doesn't exist. +As we mentioned before, a controller can interface with more than just a foundation service. They can interface with higher-order business logic functions. For instance, a processing service may offer an `Upsert` routine. In this case, a typical Http Verb wouldn't be able to satisfy a combinational routine such as an `Upsert`. In this case, we introduce custom http verbs that can support the aforementioned operation. -It is helpful to notify our consumers if we modify instead of adding which operation we choose. But that's a case-by-case implementation, and more often than ever, consumers don't really care to learn that piece of information. The same idea applies to other languages non-foundation services may use, such as `Process` or `Calculate` or any other business-specific language higher or hyper-advanced order services may choose. +Custom http verbs can be anything as long as it doesn't conflict with an existing verb or contains non-supported characters. For instance, if we have a system that generate barcodes for some products, the generation process doesn't quite fit a `POST` or a `GET`. Especially when we factor in that these http verbs/methods might already be used for the same route for storing and retrieving barcodes from the system. This is a case where a new http verb can be introduced as `GENERATE` where you can pass the same route with a different verb that a server would understand. + +We introduced this capabilty in RESTFulSense library where you can simply define your controller method as follows: + +```csharp + [HttpCustom("GENERATE")] + public ActionResult GenerateBarcode() => + this.barcodeProcessingService.Generate(); +``` +A consumer client can call that endpoint also as follows: + +```csharp + Barcode generatedBarcode = + await restfulHttpClient.SendHttpRequestAsync( + method: "GENERATE", + relativeUrl: "api/barcodes", + cancellationToken: someOrNoneCancellationToken); +``` + +The same operations can also be done without using a particular library with the native `HttpClient`. #### 3.1.1.2.0.1 Similar Verbs -Sometimes, especially with basic CRUD operations, you will need the same Http Verb to describe two different routines. For instance, integrating with both `RetrieveById` and `RetrieveAll` resolves to a `Get` operation on the RESTful realm. In this case, each function will have a different name while maintaining the same verb as follows: +Sometimes, especially with basic CRUD operations, you will need the same Http Verb to describe two different routines. For instance, integrating with both `RetrieveById` and `RetrieveAll` resolves to a `Get` operation on the RESTful realm. In this case, each function will have a different name and route while maintaining the same verb as follows: ```csharp [HttpGet] @@ -58,6 +78,12 @@ As you can see above, the differentiator here is simultaneously the function nam #### 3.1.1.2.0.2 Routes Conventions RESTful API controllers are accessible through routes. A route is simply a URL combined with an Http verb, so the system knows which routine it needs to call to match that route. For instance, if I need to retrieve a student with id `123`, my API route would be `api/students/123`. And if I want to retrieve all the students in some system, I could just call `api/students` with the `GET` verb. +##### 3.1.1.2.0.2.0 Nouns & Verbs +Routes should never have verbs in them. That's the responsibility of the http verb. For instance, we should never name a route as such: `api/students/get` that is a violation of the naming conventions of The Standard. The rule here is that routes should also have nounes, and http methods should always have verbs. Http methods with customization as mentioned above could have endless number of custom methods against the very same route. + +The Standard also enforces the single-noun principle. Routes should not combine mulitple nouns. For instance, instead of saying: `api/studentsubmissions` we should say: `api/student-submissions`. +On that same line, retreiving submissions for a particular student can be represented as follows: `api/students/{studentId}/submissions` the breakdown here is necessary to imply the intersection between resources compared to pulling everything in storage. + ##### 3.1.1.2.0.2.0 Controller Routes The controller class in a simple ASP.NET application can be set at the top of the controller class declaration with a decoration as follows: @@ -72,7 +98,7 @@ public class StudentsController The route there is a template that defines the endpoint to start with `api` and trail by omitting the term "Controller" from the class name. So `StudentsController` would end up being `api/students`. All controllers must have a plural version of the contract they are serving. Unlike services where we say `StudentService`, controllers would be the plural version with `StudentsController`. -##### 3.1.1.2.0.2.1 Routine Routes +##### 3.1.1.2.0.2.1 Routine/Method-Specific Routes The same idea applies to methods within the controller class. As the code snippet above says, we decorated `GetStudentByIdAsync` with an `HttpGet` decoration with a particular route identified to append to the existing controller overall route. For instance if the controller route is `api/students`, a routine with `HttpGet("{studentId})` would result in a route that looks like this: `api/students/{studentId}`. The `studentId` then would be mapped in as an input parameter variable that *must* match the variable defined in the route as follows: @@ -85,7 +111,7 @@ public async ValueTask> GetStudentByIdAsync(Guid studentId } ``` -But sometimes, these routes are not just URL parameters. Sometimes, they contain a request within them. For instance, we want to post a library card against a particular student record. Our endpoint would look like `api/students/{studentId}/librarycards` with a `POST` verb. In this case, we have to distinguish between these two input parameters with proper naming as follows: +But sometimes, these routes are not just URL parameters. Sometimes, they contain a more specific parent resources information within them. For instance, we want to post a library card against a particular student record. Our endpoint would look like `api/students/{studentId}/librarycards` with a `POST` verb. In this case, we have to distinguish between these two input parameters with proper naming as follows: ```csharp [HttpPost("{studentId}/librarycards")] @@ -95,7 +121,7 @@ public async ValueTask> PostLibraryCardAsync(Guid stud } ``` -##### 3.1.1.2.0.2.2 Plural Singular Plural +##### 3.1.1.2.0.2.2 Plural-Singular-Plural When defining routes in a RESTful API, it is important to follow the global naming conventions for these routes. The general rule is to access a collection of resources, then target a particular entity, then again access a collection of resources within that entity, and so on and so forth. For instance, in the library card example above, `api/students/{studentId}/librarycards/{librarycardId}`, we started by accessing all students and then targeting a student with a particular ID. We wanted to access all library cards attached to that student and then target a very particular card by referencing its ID. That convention works perfectly in one-to-many relationships. But what about one-to-one relationships? Let's assume a student may have one and only one library card at all times. In which case, our route would still look something like this: `api/students/{studentId}/librarycards` with a `POST` verb, and an error would occur as `CONFLICT` if a card is already in place regardless of whether the Ids match or not. @@ -126,6 +152,42 @@ public async ValueTask> GetAllLibraryCards() The same idea applies to `POST` for a model. Instead of posting towards: `api/students/{studentId}/librarycards` - we can leverage the contract itself to post against `api/librarycards` with a model that contains the student id within. This flat-route idea can simplify the implementation and aligns perfectly with the overall theme of The Standard. We are keeping things simple. +##### 3.1.1.2.0.2.3 X-WWW-Form-UrlEncoded Parameters +The Standard enfornces the concept of single-entity. For instance, we can't have a method as follows in a Standard-compliant system: +```csharp +ValueTask GetTeachersByStudentAsync(Student student); +``` +The above is considered a violation because the service that supports this routine explicity handles multiple models or entity types. But The Standard also permits passing primitive parameters such as `string`, `bool` or any other primitive or native type. Controllers/API can also support the same pattern though x-www.form-urlencoded parameters as follows: + +On the controller side, you can implement `x-www-form-urlencoded` as follows: + +```csharp + [HttpPost("login")] + [Consumes("application/x-www-form-urlencoded")] + public async ValueTask> PostLoginUserAsync( + [FromForm] string username, + [FromForm] string password) + { + .... + } +``` +On the consumer side, the implementation would be: + +```csharp + var formUrlEncodedContent = + new FormUrlEncodedContent(new[] + { + new KeyValuePair("username", username), + new KeyValuePair("password", password) + }); + + HttpResponseMessage httpResponseMessage = + await this.apiClient.ExecuteHttpCallAsync(this.httpClient.PostAsync( + requestUri: $"{userAuthenticationsRelativeUrl}/login", + content: formUrlEncodedContent)); +``` +The rule to Services applies to Controllers as well, a routine at this level cannot accept more than 3 parameters - and beyond that point engineers must design the system to accept an actual entity or model and return the same model in the response. + ### 3.1.1.2.1 Codes & Responses Responses from an API controller must be mapped towards codes and responses. For instance, if we are trying to add a new student to a schooling system. We are going to `POST` a student, and in return, we receive the same body we submitted with a status code `201`, which means the resource has been `Created`. @@ -428,4 +490,4 @@ Acceptance tests are also implemented after the fact, unlike unit tests. An endp [*] [Acceptance Tests (Part 1)](https://www.youtube.com/watch?v=WWN-9ahbdIU) -[*] [Acceptance Tests (Part 2)](https://www.youtube.com/watch?v=ANqj9pldfso) \ No newline at end of file +[*] [Acceptance Tests (Part 2)](https://www.youtube.com/watch?v=ANqj9pldfso)