Difference between revisions of "Coding standards"

From DarkWiki
Jump to: navigation, search
(URL structure)
(Verbs)
 
(62 intermediate revisions by the same user not shown)
Line 1: Line 1:
 
==Object properties==
 
==Object properties==
  
===Getters===
+
===Getters ("Accessors")===
  
 
'''Avoid throwing exceptions.''' A getter should provide access to the field, and should not result in an exception. There are occasions when this rule can be relaxed (such as indeterminable exceptions like OutOfMemoryException, or deliberate exceptions such as IllegalStateException), but things like NullPointerExceptions should not occur.
 
'''Avoid throwing exceptions.''' A getter should provide access to the field, and should not result in an exception. There are occasions when this rule can be relaxed (such as indeterminable exceptions like OutOfMemoryException, or deliberate exceptions such as IllegalStateException), but things like NullPointerExceptions should not occur.
  
'''Must be repeatable.''' Each call to a getter should result in the exact same response. As a general rule, you should not create new objects. If you do, you should make sure they are always guaranteed to be equal (this may be hard if you are not responsible for the other code).
+
'''Must be repeatable.''' Each call to a getter should result in the exact same response. As a general rule, you should not create new objects. If you do, you should make sure they are always guaranteed to be equal (this may be hard if you are not responsible for the other code). The desired behaviour is that of a ''const'' method in C++, so that calling the method on an object does not change the state of the object.
  
===Setters===
+
===Setters ("Mutators")===
  
 
'''Order cannot matter'''. The order in which setters are called should have no effect on the end result. Calling setA() then setB() should end with the same result as setB() followed by setA().
 
'''Order cannot matter'''. The order in which setters are called should have no effect on the end result. Calling setA() then setB() should end with the same result as setB() followed by setA().
 +
 +
===Naming convention===
 +
 +
{|
 +
!Pattern!!Notes
 +
|-
 +
|get<field>()||Get the value of 'field'. It does not modify anything.
 +
|-
 +
|is<field>()||Get the value of a boolean named 'field'. It does not modify anything.
 +
|-
 +
|has<field>()||Get the value of a boolean named 'field'. It does not modify anything.
 +
|-
 +
|find<criteria>()||Find a set of objects. It will always return a collection (typically a list), which might be empty. It does not return null.
 +
|-
 +
|create<criteria>()||Create a single object based on a set of parameters. This is a ''factory method''.
 +
|}
  
 
==RESTful URL endpoints==
 
==RESTful URL endpoints==
 +
 +
===RESTlike versus RESTful===
 +
 +
Pure REST''ful'' (i.e. [https://en.wikipedia.org/wiki/HATEOAS HATEOAS]) is a heavyweight abstraction (and enthused by the puritanical bore) that places demands on clients that they often simply cannot accommodate efficiently. Therefore, a REST''like'' approach is the happy medium, where we use the traditional contract approach (a specified set of URLs and payload definitions). This simple approach is understood by all and can be achieved in the smallest or oldest of architectures, but is at the expense of automation of certain developer tooling.
 +
 +
<blockquote>When developing an endpoint, focus on the client as that is your customer; the easier it is to consume, the better your reputation.</blockquote>
 +
 +
Assuming that the length of time to develop client-side code is less than the time for which it is expected to be used, efficiency is the most important part of an API. Understanding is secondary. The second is a matter of the client-side developer learning once. Once done and let loose in the live environment, efficient operation is by far the most important consideration. Reducing the number of calls to achieve something is important. Savings of 10ms are important. Consider the object graph when designing your API, but it is only one aspect you need to consider; focus on ensuring there is no recursion, circular dependencies, etc. For example, provide a mechanism to get users, to get groups, and to get membership.
 +
 +
  /user-management/users/111
 +
  /user-management/users/111/groups
 +
  /user-management/groups/999
 +
  /user-management/groups/999/members
 +
 +
Lastly, in anything other than trivial situations, it is rare that ''any or all'' parts of the object graph can be updated by clients. In most cases, you'll find clients need to update only a tiny portion of the graph. You should only provide interfaces for those that are needed.
  
 
===URL structure===
 
===URL structure===
  
The API endpoints should incorporate the plural of the entity (i.e. "users" rather than "user").
+
The API endpoints should incorporate the plural of the entity (i.e. "users" rather than "user"). An entity is always a '''noun'''.
  
  /api/<entities>/<entityId>
+
  /<category>/<entities>/<entityId>
  /api/<entitiesA>/<entityIdA>/<entitiesB>/<entityIdB>/<entitiesC>/<entityIdC>
+
  /<category>/<entitiesA>/<entityIdA>/<entitiesB>/<entityIdB>/<entitiesC>/<entityIdC>
  
When the identifier is something other than an identifier, we alter the path variable accordingly:
+
When the entity id is something other than an identifier, we alter the path variable accordingly to effectively name the collection:
  
   /api/<entitiesByName>/<name>
+
   /<entitiesByName>/<name>
   /api/<entitiesBySize>/<size>
+
   /<entitiesBySize>/<size>
   /api/<entitiesById>/<entityId>
+
   /<entitiesById>/<entityId>
  
 
As the "ById" pattern is so common, we choose to shorten it by removing the 'ById', so that it becomes:
 
As the "ById" pattern is so common, we choose to shorten it by removing the 'ById', so that it becomes:
  
   /api/<entities>/<entityId>
+
   /<entities>/<entityId>
 +
 
 +
Optional parameters (such as those used for filtering or ordering results, for example) should not appear in the URL path, but in the query parameters.
 +
 
 +
  /<entities>?text=FRED&orderBy=firstName
  
Optional parameters (such as those used for ordering results, for example) should not appear in the URL path, but in the query parameters.
+
===Examples===
  
 
Here are some examples:
 
Here are some examples:
  
  /api/users/{userId}
+
  /user-management/users/76516745
  /api/users''ById''/{userId}
+
  /user-management/users''ById''/76516745
  /api/usersByName/{name}
+
  /user-management/usersByName/fbloggs
  /api/users/{userId}/features
+
  /user-management/users/76516745/features
  /api/users/{userId}/features/{featureId}
+
  /user-management/users/76516745/features/{featureId}
  /api/groups/{groupId}/users?sort=name
+
  /user-management/groups/1872/users?sort=name
 +
 
 +
===Verbs===
 +
 
 +
{| class="wikitable"
 +
!Method!!Purpose!!Example!!Notes
 +
|-
 +
|POST||'''C'''reate||<pre>/users</pre> <pre>/users/<userId></pre>||If the URL contains a ''userId'', it should try to use that identifier. This behaviour is useful for import/export between systems.
 +
|-
 +
|GET||'''R'''ead||<pre>/users/<userId></pre>||
 +
|-
 +
|PUT||'''U'''pdate||<pre>/users/<userId></pre>||If the entity in the body contains a different ''userId'', it might try to effectively rename/move the entity.
 +
|-
 +
|PATCH||'''U'''pdate|||<pre>/users/<userId></pre>||Smilar to ''PUT'', but this accepts partial information and effectively merges it into an existing entity.
 +
|-
 +
|DELETE||'''D'''elete||<pre>/users/<userId></pre>||
 +
|}
 +
 
 +
Other actions should be implemented so that the "verb" appears at the end of the URL. For example, if there is a situation where you want to '''play''' (verb) a certain song, the URL would look something like:
 +
 
 +
  /song-book/songs/197263541675243/'''play'''
 +
 
 +
Of course, you'd rarely need a server to actually play the song. Rather, you're wanting the server to send the browser the file, and it will be the browser that plays it. i.e. it would be a GET. It is important to try to keep the number of verbs to an absolute minimum - perhaps even try to irradicate them, favouring only using the standard GET, PUT, POST, and DELETE.
 +
 
 +
===Response codes===
 +
 
 +
This table identifies the most common HTTP status codes that are used in RESTful exchanges. Rarely are all applied; it depends very much on the endpoint and its function.
 +
 
 +
{| class="wikitable"
 +
!Code!!Name!!Notes
 +
|-
 +
|200||OK||Everything was ok.
 +
|-
 +
|201||CREATED||The new resource was created.
 +
|-
 +
|202||ACCEPTED||The request has been accepted but has not yet finished. Useful for asynchronous flows.
 +
|-
 +
|207||MULTI_STATUS||The response holds information about multiple resources (perhaps in multiple statuses). Useful for batch processing responses.
 +
|-
 +
|400||BAD_REQUEST||There was something that made the request invalid. It was not possible to read the request. e.g. An incorrectly formatted date, or other such validation error.
 +
|-
 +
|401||UNAUTHORIZED||The resource is protected requires authentication.
 +
|-
 +
|403||FORBIDDEN||Even though you may be authenticated, you are not permitted access to that resource.
 +
|-
 +
|404||NOT_FOUND||The resource does not exist. i.e. The URL that has been used does not exist.
 +
|-
 +
|409||CONFLICT||The resource already exists.
 +
|-
 +
|422||UNPROCESSABLE_ENTITY||The request was understood, but it could not be executed. e.g. When the resource that is being edited is not in a state that allows it. i.e. The request is valid, but the state is not.
 +
|-
 +
|423||LOCKED||The resource is not yet ready for reading (as it's locked, or still being created by another process).
 +
|-
 +
|500||INTERNAL_ERROR||A bug that needs to be fixed by the developer.
 +
|-
 +
|501||NOT_IMPLEMENTED||The endpoint does nothing as it hasn't been implemented.
 +
|}
 +
 
 +
Classes of code and their meaning to the behaviour and subsequent actions:
  
 
{| class="wikitable"
 
{| class="wikitable"
!Method!!Purpose!!Example
+
!Code!!Notes
 
|-
 
|-
|POST||'''C'''reate||<pre>/api/users</pre>
+
|2xx||It worked.
 
|-
 
|-
|GET||'''R'''ead||
+
|3xx||These (redirects) are generally handled automatically.
 
|-
 
|-
|PUT||'''U'''pdate||
+
|4xx||Client needs to handle the problem.
 
|-
 
|-
|DELETE||'''D'''elete||
+
|5xx||Server needs to handle the problem.
 
|}
 
|}
 +
 +
More information can be found here: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes

Latest revision as of 07:40, 17 August 2023

Object properties

Getters ("Accessors")

Avoid throwing exceptions. A getter should provide access to the field, and should not result in an exception. There are occasions when this rule can be relaxed (such as indeterminable exceptions like OutOfMemoryException, or deliberate exceptions such as IllegalStateException), but things like NullPointerExceptions should not occur.

Must be repeatable. Each call to a getter should result in the exact same response. As a general rule, you should not create new objects. If you do, you should make sure they are always guaranteed to be equal (this may be hard if you are not responsible for the other code). The desired behaviour is that of a const method in C++, so that calling the method on an object does not change the state of the object.

Setters ("Mutators")

Order cannot matter. The order in which setters are called should have no effect on the end result. Calling setA() then setB() should end with the same result as setB() followed by setA().

Naming convention

Pattern Notes
get<field>() Get the value of 'field'. It does not modify anything.
is<field>() Get the value of a boolean named 'field'. It does not modify anything.
has<field>() Get the value of a boolean named 'field'. It does not modify anything.
find<criteria>() Find a set of objects. It will always return a collection (typically a list), which might be empty. It does not return null.
create<criteria>() Create a single object based on a set of parameters. This is a factory method.

RESTful URL endpoints

RESTlike versus RESTful

Pure RESTful (i.e. HATEOAS) is a heavyweight abstraction (and enthused by the puritanical bore) that places demands on clients that they often simply cannot accommodate efficiently. Therefore, a RESTlike approach is the happy medium, where we use the traditional contract approach (a specified set of URLs and payload definitions). This simple approach is understood by all and can be achieved in the smallest or oldest of architectures, but is at the expense of automation of certain developer tooling.

When developing an endpoint, focus on the client as that is your customer; the easier it is to consume, the better your reputation.

Assuming that the length of time to develop client-side code is less than the time for which it is expected to be used, efficiency is the most important part of an API. Understanding is secondary. The second is a matter of the client-side developer learning once. Once done and let loose in the live environment, efficient operation is by far the most important consideration. Reducing the number of calls to achieve something is important. Savings of 10ms are important. Consider the object graph when designing your API, but it is only one aspect you need to consider; focus on ensuring there is no recursion, circular dependencies, etc. For example, provide a mechanism to get users, to get groups, and to get membership.

 /user-management/users/111
 /user-management/users/111/groups
 /user-management/groups/999
 /user-management/groups/999/members

Lastly, in anything other than trivial situations, it is rare that any or all parts of the object graph can be updated by clients. In most cases, you'll find clients need to update only a tiny portion of the graph. You should only provide interfaces for those that are needed.

URL structure

The API endpoints should incorporate the plural of the entity (i.e. "users" rather than "user"). An entity is always a noun.

/<category>/<entities>/<entityId>
/<category>/<entitiesA>/<entityIdA>/<entitiesB>/<entityIdB>/<entitiesC>/<entityIdC>

When the entity id is something other than an identifier, we alter the path variable accordingly to effectively name the collection:

 /<entitiesByName>/<name>
 /<entitiesBySize>/<size>
 /<entitiesById>/<entityId>

As the "ById" pattern is so common, we choose to shorten it by removing the 'ById', so that it becomes:

 /<entities>/<entityId>

Optional parameters (such as those used for filtering or ordering results, for example) should not appear in the URL path, but in the query parameters.

 /<entities>?text=FRED&orderBy=firstName

Examples

Here are some examples:

/user-management/users/76516745
/user-management/usersById/76516745
/user-management/usersByName/fbloggs
/user-management/users/76516745/features
/user-management/users/76516745/features/{featureId}
/user-management/groups/1872/users?sort=name

Verbs

Method Purpose Example Notes
POST Create
/users
/users/<userId>
If the URL contains a userId, it should try to use that identifier. This behaviour is useful for import/export between systems.
GET Read
/users/<userId>
PUT Update
/users/<userId>
If the entity in the body contains a different userId, it might try to effectively rename/move the entity.
PATCH Update
/users/<userId>
Smilar to PUT, but this accepts partial information and effectively merges it into an existing entity.
DELETE Delete
/users/<userId>

Other actions should be implemented so that the "verb" appears at the end of the URL. For example, if there is a situation where you want to play (verb) a certain song, the URL would look something like:

 /song-book/songs/197263541675243/play

Of course, you'd rarely need a server to actually play the song. Rather, you're wanting the server to send the browser the file, and it will be the browser that plays it. i.e. it would be a GET. It is important to try to keep the number of verbs to an absolute minimum - perhaps even try to irradicate them, favouring only using the standard GET, PUT, POST, and DELETE.

Response codes

This table identifies the most common HTTP status codes that are used in RESTful exchanges. Rarely are all applied; it depends very much on the endpoint and its function.

Code Name Notes
200 OK Everything was ok.
201 CREATED The new resource was created.
202 ACCEPTED The request has been accepted but has not yet finished. Useful for asynchronous flows.
207 MULTI_STATUS The response holds information about multiple resources (perhaps in multiple statuses). Useful for batch processing responses.
400 BAD_REQUEST There was something that made the request invalid. It was not possible to read the request. e.g. An incorrectly formatted date, or other such validation error.
401 UNAUTHORIZED The resource is protected requires authentication.
403 FORBIDDEN Even though you may be authenticated, you are not permitted access to that resource.
404 NOT_FOUND The resource does not exist. i.e. The URL that has been used does not exist.
409 CONFLICT The resource already exists.
422 UNPROCESSABLE_ENTITY The request was understood, but it could not be executed. e.g. When the resource that is being edited is not in a state that allows it. i.e. The request is valid, but the state is not.
423 LOCKED The resource is not yet ready for reading (as it's locked, or still being created by another process).
500 INTERNAL_ERROR A bug that needs to be fixed by the developer.
501 NOT_IMPLEMENTED The endpoint does nothing as it hasn't been implemented.

Classes of code and their meaning to the behaviour and subsequent actions:

Code Notes
2xx It worked.
3xx These (redirects) are generally handled automatically.
4xx Client needs to handle the problem.
5xx Server needs to handle the problem.

More information can be found here: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes