REST is architectural style, not standard
REST is protocol agnostic
JSON isn't rest, HTTPs isn't even REST
CLIENT changes STATE depending upon the representation of the resource you're accessing (from the Server or otherwise)
and that is REST (Representational State Transfer)
REST is defined by 6 Constraints (constraints can have positive and negative impacts)
Resource identifiers (the URI where the resources can be found)
each resource has its own URI
Nouns: things, not actions (actions are meant for the HTTP methods)
helps with predictibility
represent hierachy when naming resources - api/authors/{authorId}/books
filters, sorting orders...aren't resources (should be done via Query string) -api/authors?orderBy=name
api/authors/{authorId}/totalamountofpages
HTTP method (POST, PUT, GET)
Payload (representation: media types (application JSON))
Impelementing Outer Facing Contract
Routing matches a request URI to an action on the controller
2 Middleware need injected:
app.useRouting() - marks the position in the middleware pipeline where a routing decision is made
app.UseAuthorization()
app.UseEndpoints() - marks the position in the middleware pipeline where the selected endpoint is executed
Convention-based Routing (typically used for Web Applications)
app.UseEndpoints(endpoints => { endpoints.MapControllerRoute(name: "default", pattern: "{controller=home}/{action=Index}/{id?}"; endpoints.MapRazorPages()});
Attribute-based Routing (typically used for APIs)
app.useEndpoints(endpoints => { endpoints.MapControllers();});
No conventions are applied ... so use attributes. Use attributes at controller and action level: [Route], [HttpGet]...
Http Cache Relies on correct use of Http methods
Status codes should describe the results of the Resource OUTER FACING layer of the API (not concerned with levels lower than it)
5 levels:
Errors = Consumer passes invalid data to the API and the API correctly rejects it (level 400 codes). Do not contribute to API availability
Faults = API fails to return a response to a valid request made by the consumer (level 500 status codes). DO contribute to the overall API availability
Entity Model represents database rows as Objects
Whenever mapping fields from one class to another ... try using Automapper (better to use AutoMapper.Extensions.Microsoft.DependencyInjection)
need to Configure Services in Startup.cs for it --- services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
Handling Faults
try catches aren't expensive on their own...but once an exception IS THROWN ... it becomes detriment to performance.
check Configure method in Startup.cs
handling HEAD
HEAD is identical to GET, with the notable difference that the API shouldn't return a response Body (can be used to obtain info on the response...whether the resource is still valid, accessible at all etc...)
can just do the following (on same method)
[HttpGet()]
[HttpHead]
via request body
via url query strings
by default ASP.NET Core attempts to use the complex object model binder
Filtering --- allows you to be precise by adding filters until you get exactly the results you want
Searching --- allows you to go wider ... you don't know exactly which items will be in the collection
Filtering and Search options are not part of the resource ... only allow filtering on fields that are part of the resource.
var collection = _context.Authors as IQueryable(Author); (turn a table into a Queryable collection)
Deferred Execution
Query execution occurs sometime after the query is constructed
A query variable stores query commands, not results
IQueryable(T) creates an expression tree
Execution is deferred until the query is iterated over
foreach loop
ToList(), ToArray(), ToDictionary(); Singleton queries (First, Last, etc...)
Shouldn't allow POST with url containing the id ... (Framework handles this for us returning a 405 method not found)
a method is considered safe when it doesn't change the resource representation
a method is considered idempotent when it can be called multiple times with the same result
GET = both
Options and head = Both
Post = neither of them
DELETE = not safe BUT it's idempotent
For a collection of Resources...just create a new Resource Type --> create a new Controller
Supporting Options
handles communication about how to speak to our API
[HttpOptions]
Validation
Validate input, not output (validate input with DataAnnotations)
Class-level input-validation with IValidatableObject
Checking Validation Rules
Built in ModelState is used
Reporting Validation errors
Response status should be 422 (client level error)
When a controller is annatated with ApiController attribute it will automatically return a 400 BadRequest on validation errors
Updating Resources
PUT for full updates
all resource fields are either overwritten or set to their default values
PATCH for partial updates
allows sending over change sets via JsonPatchDocument
UPSERTING create a resource with PUT or PATCH
Updating Collection Resources
Upserting
PUT = ideompotent
Partially updating a Resource
Patch
(Http PATCH is for partial updates)
preferred over PUT because of performance reasons
return type for PATCH should be sent with media type "application/json-patch+json"
6 possible JSON Patch Operations
an abstraction that reduces complexity and aims to make the code safe for the repository impelentation, persistance ignorant
Advantages:
Persistance ignorant
switching out the persistance technology is not the main purpose... Choosing the best one for each repository method is.
we're working on a contract, not an implementation (in regards to having blank method body in repository)
always have a set of methods matching the required functionality and call them, even if they don't do anything in the current implementation.
Implementation:
Steps for building in Development Mode (will make everything pop up / log in a console window)
Resource Parameters
create class "AuthorsResourceParameters" inside separate folder
this allows us to just pass this in as a method parameter inside controller
Useful because ... IF we decide to add more parameters...we can just add the parameters in one spot (the class body)
Repository Pattern (see above)
Profiles (related to AutoMapper)
Crate a Profiles folder and then a ClassProfile for each class you will be saving into database
ClassProfile : Profile (using AutoMapper)
Entities folder contains classes that will persist in data store
Model folder contains Dto classes (used for mapping and returning from our Controller methods so that we don't have to return entire Entity)
possible Nuget Packages:
cacheable Constraint = Each response message must explicitly state if it can be cached or not
considered best-practice to implement paging on each collection ... or at least on those Resources that can also be created? Parameters for paging passed through via the query string. Users should be able to specify the page # and page size ( page size should be limited). IF no parameters ...just return the first page by default. PAGING should go all the way through to the data store ... IE don't wait to Page the data in the controller .... do it in database
Deferred Execution helps with this = Query variable itself never holds the query results themselves ... Query execution occurs sometime after the query is constructed (use IQueryable
Returning MetaData = think about returning links to the previous and next pages ... can include additional information (total count, amount of pages, etc...)
Use a custom header, like X-Pagination on the reponse
for returning pagination collections = created a Custom PagedList
Allowing sorting on resource fields, not on fields that lie beneath that layer
Return the OrderBy clause as query string in pagination links
Data Shaping
ExpandoObject = Object whose members can be dynamically added or removed at RunTime
Data shaping allows consumer of the API to choose the resource fields (instead of returning all the fields of the Author for example) fields =id, name (shape on fields of the resource, not on the fields of lower-level layer objects)
Dynamically creating an object from our ObjectDtos (at RunTime) to return in responses
thread pool starvation (common cause of slow, bottlenecked code) -- where your thread pool is too busy exhausted and is unavailable to jump around and pick up new requests
Possible issues:
Redis bulk data management
Logging Output
Serilog Library -- Console Standard Out (console output was slow)
Cancelling redundant tasks -- Polly
Polly -- helps you easily control tasks such as timeouts, retrying failed code, and some other powerful functionality
Polly settings:
HTTP cache (holy Grail)
response caching middleware
every response should define itself as cacheable or not
cache is separate component that sits between the client (user) and the API itself
Receives repsonses from the API and stores them if they are deemed cacheable
Accepts requests from consumer to the API
It's the middle-man of request-response communication
3 Cache Types:
Reponse Cache Attribute and Middleware
state for each resource whether or not it's cacheable
Cache-Control: max-age=120 (says it's cacheable only for 120 seconds)
doesn't actually cache anything ... (need a cache store)
Postman by default sends a no-cache request (go into settings and turn off "send-no-cache header"
ResponseCaching Middleware
services.AddResponseCaching(); inside Startup.cs
app.UseResponseCaching(); inside Startup.cs in Configure method (make sure it's before all the other app.Use ones)
Cache Profiles -- add into services.AddControllers() in Startup.cs
Expiration Model
Validation Model (not supported by DOTnet core middleware)
Expiration and Validation Models are usually combined
Private cache
As long as the response hasn't expired (Isn't stale), thate response can be returned from the cache
Reduces communication with the API (including response generation), reduces bandwidth requirements
If it has expired, the API is hit
Public Cache
As long as the response hasn't expired (isn't stale) that response can be returned from the cache
Reduces bandwidth requirements between cache and API, dramatically reduces request to the API
If it has expired, the API is hit
Bandwidth between cache and API and response generation is potentially reduced
Cache-Control Directives
Response:
Freshness (max-age, s-maxage) ... can set different for private / public
Cache Type (public, private)
Validation (no-cache, must-reavalidate, proxy-reavalidate)
other (no-store, no-transform) no-store = for confidentiality reasons
Request (by Client):
Freshness (max-age, min-fresh, max-stale)
Validation (no-cache)
Other (no-store, no-transform, only-if-cached)
Supporting Http Cache
ETags are preferred over dates as they are strong validators
Marvin.Cache.Headers (useful Header middleware from tutorial)
IF a response is stale, a cache must validate it using ETags
MSFT Response cache good for simple use cases ... like Expiration ... ONCE VALIDATION comes into play ... (Add in Marvin.Cache middleware)
Cache Stores and CDNs
most cache stores are full-blown cache servers, not pieces of middleware
private caches LIVE on the CLIENT
Xamarin ... probably needs CacheCow.Client
Shared Caches = gateway or proxy caches
Varnish, Apache Traffic Server, Squid
CDNs
extensively use caching: Http cache (Azure CDN, Cloudfare, Akamai)
Not recommended to implement cache-control at API level ... instead ensure API can return Cache-Control headers and supports expiration / validation models (Combine that with a cache server or CDN)
Cache Invalidation
wiping a response from the cache because you know it isn't the correct version anymore (a lot is automated) (responses go stale, ETags get updated)
Concurrency Strategies
Pessimistic concurrency (not possible in REST) ... Resource Locked
Optimistic concurrency (supported with Etags)
Token is returned together with resource
The udpate can happen as long as the token is still valid
The Marvin.Cache Nuget package handles this stuff for us
use ETags as tokens/validators for an optimistic concurrency strategy
Send as If-Match header value
on mismatch, 412 Precondition Failed will be returned
Map (using IMapper) between Entities (classes) and Dtos (Model folder (what we return from our controllers))
All fields should be updated as the Result of a put request
PATCH for partial updates
validation: to minimize code duplication ... we can work with abstract base classes and mark common properties with virtual to allow for their own implementation
Vendor-specific Media Types
application/vnd.marvin.hateoas+json
need to register / set-up valid OutputFormatters in startup.cs
there should be only one suffix per media type, and only officially registered suffixes should be used
always provide a default representation that will be returned when no semantic information is passed through (eg: application/json)
HATEOAS (tells User what they can do with our API)
HATEOS links should be return only when the correct media type is requested
(Hypermedia as the Application State)
helps with evolvability and self descriptiveness
if an API fails @ HATEOAS ... then a user of the API must know more than they should ... if an API fails @ HATEOAS ... the API cannot evolve separately of consuming applications
Adapting to change:
HATEOAS for changes to functionality and business rules
Versioned media types (until code on demand feasible)
creating a Dto (to return from requests)
1) create a Model class (dto)
2) add a Mapping profile (or just a CreateMap () in already existing Mapping Profile)
Advanced content negotation
Caching and concurrency
Not recommended to implement cache-control at API level ... instead ensure API can return Cache-Control headers and supports expiration / validation models (Combine that with a cache server or CDN)
Paging, sorting, data shaping