|
|
# inLeague Development Manual (CFML)
|
|
|
|
|
|
This document governs best practices for writing CFML for inLeague applications. It is distinct from the [Coding Style Guide](https://github.com/Ortus-Solutions/coding-standards "Ortus Solutions Style Guides") from Ortus that is concerned primarily with formatting and readability; this document is similar to [CFSnippets](https://cfsnippets.com/ "Michael Born's CFSnippets") -- a collection of solutions to development challenges sourced from our experience and the community. |
|
|
\ No newline at end of file |
|
|
This document governs best practices for writing CFML for inLeague applications. It is distinct from the [Coding Style Guide](https://github.com/Ortus-Solutions/coding-standards "Ortus Solutions Style Guides") from Ortus that is concerned primarily with formatting and readability; this document is similar to [CFSnippets](https://cfsnippets.com/ "Michael Born's CFSnippets") -- a collection of solutions to development challenges sourced from our experience and the community.
|
|
|
|
|
|
# Table of Contents
|
|
|
|
|
|
1. [Writing API Endpoints](#writing-api-endpoints)
|
|
|
|
|
|
## Writing API Endpoints
|
|
|
|
|
|
All API endpoints are Coldbox events in handlers extending **BaseHandler**. Each endpoint has the following considerations:
|
|
|
|
|
|
1. [Authentication](#api-endpoint-authentication)
|
|
|
2. [Authorization](#api-endpoint-authorization)
|
|
|
3. [Routing](#api-endpoint-routing)
|
|
|
4. [Caching](#api-endpoint-caching)
|
|
|
5. [REST Convention](#api-rest-convention)
|
|
|
6. [Function](#api-endpoint-function)
|
|
|
|
|
|
### API Endpoint Authentication
|
|
|
|
|
|
The inLeague API supports several styles of authentication, but only one of them is appropriate for new endpoints:
|
|
|
|
|
|
* **JSON Web Tokens** (JWT): Specified by the handler's **preHandler** setting **prc.JWTAuth = 1**. Is also triggered by any incoming request containing the header **Authorization**, whether or not the handler explicitly declares **prc.JWTAuth**. All new API methods should use this method.
|
|
|
* Other mechanisms: Authorize.net automated API calls and AYSO automated API calls have a unique style of authentication. These can be reviewed in the relevant handlers and interceptors.
|
|
|
|
|
|
### API Endpoint Authorization
|
|
|
|
|
|
API Handler endpoints should be annotated with the **secured** (or **x-secured**) values matching the AuthService context desired, e,g.
|
|
|
|
|
|
`function index ( event, rc, prc ) secured="PlayerAdmin,DivisionDirector" {
|
|
|
....function
|
|
|
}`
|
|
|
|
|
|
The above endpoint will reject any request from a user not authorized in the PlayerAdmin or DivisionDirector roles.
|
|
|
|
|
|
*Neglecting to include the secured attribute in an API function definition will make the endpoint available to any authenticated user.*
|
|
|
|
|
|
### API Endpoint Routing
|
|
|
|
|
|
The current API routing scheme is simple, but disorganized, due to all API routes living in the `ModuleConfig` for the `v1` module. For instance, the API endpoint to list all available seasons using the `seasons` handler with the `list` function is:
|
|
|
|
|
|
`.route( "/seasons/list" )
|
|
|
.withAction( {
|
|
|
GET = "list"
|
|
|
.toHandler( "seasons" )`
|
|
|
|
|
|
*TODO*: Look at breaking up API endpoints to the relevant modules, e.g. referee-related API endpoints to the referee module and then using `.toModuleRouting()`
|
|
|
|
|
|
### API Endpoint Caching
|
|
|
|
|
|
[Coldbox Event Caching](https://coldbox.ortusbooks.com/the-basics/event-handlers/event-caching) works by caching the literal output of an event in the Coldbox template cache (which, for inLeague, is in Redis). With any API-heavy application, this is a critical component to relieve pressure on the back-end API instance. There are two ways to implement event caching. For an example, look at the **authenticate** handler function **lookupUser**, a simple function that retrieves a user based on a username (which, for inLeague, is an email address). Note that this function is not an API endpoint: it is a helper function used by other API endpoints, like the `authenticate` `GET` endpoint that serves as our login handler.
|
|
|
|
|
|
1. **Event function definition**: Specify `cache=true` and `cacheTimeout=someNumber` in the event, e.g.
|
|
|
|
|
|
`public function lookupUser( event, rc, prc, string username ) cache=true cacheTimeout=10 {`
|
|
|
|
|
|
2. **runEvent()**: When calling a Coldbox event internally, you can specify (or override) cache values using the `cache` and `cacheTimeout` arguments.
|
|
|
|
|
|
There are two considerations to keep in mind when using event caching:
|
|
|
|
|
|
1. **Every unique combination of arguments is cached separately**. In our `lookupUser` example, passing a different `username` argument to the event will create a new cache key for that event and that username. Changing any part of the query string or event arguments will result in a new cache key.
|
|
|
|
|
|
2. **Cache Eviction**. Whenever the underlying data changes, it is essential to update the cache. This is best done through the convention of an interceptor, e.g. a Quick `postSave()` interception point that will clear all related API events. The example of `lookupUser` has a scenario (however unlikely) where a request is made for a login that doesn't yet exist, but is then added before the ten minute timeout expires. We would clear the relevant cache with this snipper:
|
|
|
|
|
|
`cachebox.getCache( "template" ).clearEvent('authenticate.lookupUser','username=' & arguments.username )`
|
|
|
|
|
|
## API REST Convention
|
|
|
|
|
|
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 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).
|
|
|
|
|
|
Remember: **Fat models, skinny handlers.** |
|
|
\ No newline at end of file |