... | @@ -33,7 +33,8 @@ All API endpoints are Coldbox events in handlers extending **BaseHandler**. Each |
... | @@ -33,7 +33,8 @@ All API endpoints are Coldbox events in handlers extending **BaseHandler**. Each |
|
3. [Routing](#api-endpoint-routing)
|
|
3. [Routing](#api-endpoint-routing)
|
|
4. [Caching](#api-endpoint-caching)
|
|
4. [Caching](#api-endpoint-caching)
|
|
5. [REST Convention](#api-rest-convention)
|
|
5. [REST Convention](#api-rest-convention)
|
|
6. [Function](#api-endpoint-function)
|
|
6. [Validation](#validating-api-requests)
|
|
|
|
7. [Function](#api-endpoint-function)
|
|
|
|
|
|
### API Endpoint Authentication
|
|
### API Endpoint Authentication
|
|
|
|
|
... | @@ -109,8 +110,46 @@ There are two considerations to keep in mind when using event caching: |
... | @@ -109,8 +110,46 @@ There are two considerations to keep in mind when using event caching: |
|
|
|
|
|
API endpoints should respond only to the appropriate HTTP verbs, which should always conform to REST conventions. Note that they are conventions and not standards, because REST tends to advise rather than require. This concerns both the name of the resource and the path. See the [REST Cheat Sheet](https://devhints.io/rest-api) and [REST Resource Naming](https://restfulapi.net/resource-naming/) for more information.
|
|
API endpoints should respond only to the appropriate HTTP verbs, which should always conform to REST conventions. Note that they are conventions and not standards, because REST tends to advise rather than require. This concerns both the name of the resource and the path. See the [REST Cheat Sheet](https://devhints.io/rest-api) and [REST Resource Naming](https://restfulapi.net/resource-naming/) for more information.
|
|
|
|
|
|
|
|
## Validating API Requests
|
|
|
|
|
|
|
|
When validating parameters of an API request, there is a quick, lightweight strategy with some limitations, and a more thorough strategy that requires a populated domain object. There is also a hybrid.
|
|
|
|
|
|
|
|
### Lightweight API Validation (example: /api/public/handlers/user.cfc, **existsByName**)
|
|
|
|
|
|
|
|
The **existsByName** public API endpoint requires a valid date of birth and a last name field. Because it is an API endpoint, it will automatically catch a `ValidationException` (see the API BaseHandler's `catch( ValidationException e )` section).
|
|
|
|
|
|
|
|
Specify the validation constraints right in the handler:
|
|
|
|
|
|
|
|
```
|
|
|
|
public function existsByName(event, rc, prc) {
|
|
|
|
validateOrFail(
|
|
|
|
target = event.getCollection(), // this could just be **rc** but event.getCollection() is a little more explicit
|
|
|
|
constraints = {
|
|
|
|
dob: {
|
|
|
|
required: true,
|
|
|
|
type: 'date',
|
|
|
|
typeMessage: 'Invalid date of birth. Please providate a US Date (mm/dd/yyyy)'
|
|
|
|
},
|
|
|
|
lastName: {required: true}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
...(rest of function)
|
|
|
|
```
|
|
|
|
|
|
|
|
The advantage to in-line validation is that it is quick and concise. The primary disadvantage is that validation cannot refer to domain business logic: for example, if a field depends on the value of another field that may or not be provided in one request, or validation depends upon a complex business rule that is defined on an ORM component, it doesn't make sense to reproduce that validation rule in a handler.
|
|
|
|
|
|
|
|
### Model API Validation (example: /api/v1/handlers/content.cfc, **overrideUpdate**)
|
|
|
|
|
|
|
|
Model validation is the bread and butter of the cbValidation library: all of the rules are stored in a `this.constraints` property on the domain object (component). Validation rules be simple, user-defined functions, or custom-built validators.
|
|
|
|
|
|
|
|
The disadvantage to model validation is that you must have a hydrated model object before you can perform it. This requires the overhead of instantiating a Quick entity in Wirebox, and using `.fill()` or `.hydrate()` to set values from a source that has not yet been validated -- for example, if you try to `.fill()` a property with `type="date"` from a value that is not a date, CF will throw an exception. This could be a reason not to `type` your property, or it could be a reason to use the hybrid model (below).
|
|
|
|
|
|
|
|
### Hybrid API Validation (example: /api/public/handlers/user.cfc, **create**)
|
|
|
|
|
|
|
|
This approach leverages `constraints` on a domain object, but pulls those constraints into in-line, handler validation. It allows for some integration with models and reduces the likelihood of business rule duplication. It doesn't allow all the possible constraints that model validation does (e.g. method) but in-line UDFs or custom validators can be used.
|
|
|
|
|
|
## API Endpoint Function
|
|
## API Endpoint Function
|
|
|
|
|
|
The content of a single Coldbox event should be as short as possible to achieve the desired results. This is an area of concern for **DRY**: If there is a significant amount of business logic going on, that should take place in a service layer and not the API handler, unless it is absolutely, positively certain that the business logic will only ever be used in one place. Even then, envision a subsequent version of your API: if you wanted to take your endpoint and update it from **v1** to **v2**, you would ideally only concern yourself with writing the changes in business logic in the **v2** endpoint. If you would have to replicate a lot of unchanged code from your **v1** handler, probably that portion should have gone into a service layer (whether on a model object or a separate service object).
|
|
The content of a single Coldbox event should be as short as possible to achieve the desired results. This is an area of concern for **DRY**: If there is a significant amount of business logic going on, that should take place in a model or service layer and not the API handler, unless it is absolutely, positively certain that the business logic will only ever be used in one place. Even then, envision a subsequent version of your API: if you wanted to take your endpoint and update it from **v1** to **v2**, you would ideally only concern yourself with writing the changes in business logic in the **v2** endpoint. If you would have to replicate a lot of unchanged code from your **v1** handler, probably that portion should have gone into a service layer (whether on a model object or a separate service object).
|
|
|
|
|
|
Remember: **Fat models, skinny handlers.** |
|
Remember: **Fat models, skinny handlers.** |
|
|
|
\ No newline at end of file |