Code Conventions
Bridge is all grown up. With three developers, we've had to discuss and make decisions about design of the system. Here's what we have so far:
Database
Make sure we have SQL indices on any field we query on.
For all foreign keys, specify ON DELETE CASCADE and ON UPDATE CASCADE.
REST Endpoints
We're following a resource-oriented structure for the URLs that follows this pattern (assume the resource is orchards):
Read collection | GET | /orchards |
Any kind of filter, search, query or selection criteria | GET | /orchards?status=active |
Create new item in collection | POST | /orchards |
Read item in collection | GET | /orchards/<id> |
Edit item in collection | POST | /orchards/<id> |
Delete item in collection | DELETE | /orchards/<id> |
A sub-collection of an item | GET | /orchards/<id>/pineapples /orchards/<id>/pineapples/<id> etc. |
A verb not encompassed by CRUD operations of HTTP | /orchards/<id>/pineapples/<verb> /orchards/<id>pineapples/<id>/<verb> |
When an endpoint includes an identifier, and the JSON that is submitted to that URL includes a different identifier, we want to throw an exception (TODO: What exception I wonder).
Identifiers
We use the following nomenclature for different kinds of identifiers:
guid | a system generated, globally unique string to identify a record. Even if we change data stores or migrate data, we should be able to continue retrieving objects with this value |
id/identifier | a user-supplied string that has to be unique within some scope, e.g. study identifier, a string that has to be unique for all studies. We're inconsistent on the user of this even externally, e.g. you'll see id or identifier or even studyKey in documentation. |
key | a unique string (within some scope) made up of other parts, I don't know if any of these are exposed to users. Usually we separate with a colon. |
Spring
We use spring to wire up our server. We generally use the @Component, @Autowired, or @Resource annotations to minimize the amount of boilerplate code that needs to be written. Example: https://github.com/Sage-Bionetworks/BridgePF/blob/develop/app/org/sagebionetworks/bridge/services/UploadValidationService.java
For constructing objects, we prefer to use code-based Spring config. See https://github.com/Sage-Bionetworks/BridgePF/blob/develop/app/org/sagebionetworks/bridge/config/BridgeSpringConfig.java for an example. (A few of our spring configs, like proxy-beans, are still in XML.)
When creating a setter to be used by Spring injection, the setter should be set as final. This prevents subclasses from overriding the setter and causing obscure NullPointerExceptions through variable shadowing. Example:
/** Service handler for upload validation. This is configured by Spring. */ @Autowired final void setUploadValidationService(UploadValidationService uploadValidationService) { this.uploadValidationService = uploadValidationService; }
Serialization
Because no one ever updates them, don't put a serial version ID in a class file. Annotate the class with the annotation @SuppressWarnings("serial"
)
and let the compiler generate it.
Timestamps ideally are stored as ISO 8601 strings (because these preserve the time zone submitted by the user). If you need to query against the value, however, then it needs to be a Long (not the long primitive) or it'll get serialized to users as "1970-01-01T00:00:00.000Z" when not set (or null). There are cases where we're returning times to a user in a different timezone than the one they used to submit the timestamp in the first place, for this reason. For these Long values we have a custom serializer/deserializer for Jackson to convert to an ISO 8601 value in the JSON.
Documentation of fields in our Swagger API definition
There are some semantics in Swagger that are confusing, particularly in terms of when and how they apply (when an object is submitted on creation, when it is returned from the server, etc.). Here is how we are using some key property fields:
required: in theory this means the property must be present and could be "foo": null, but we would never do this, so required implies non-null (request and response). As it happens, this is also how Swagger interprets this value, despite JSON schema.
readOnly: the server generates this property and it is not expected to be returned by the client. Sometimes the Android client has wanted us to relax this, but ideally we don't since without this set, two accessors get added to the generated class.
x-nullable: indicates that even if required, the actual value can be null. We don't use this and probably don't need it and as of summer of last year, Swagger didn't implement the semantics of these three fields anyway: required to Swagger means the property is present and non-null.
In short:
required means the property will be there and the value will be non-null. You should submit the value back to the server, though the behavior if you fail to do this is undefined (mostly it'll work but there might be places where it could be interpreted as wiping out the value).
readOnly means the value never needs to be submitted by the client to the server (including it back from a response is harmless)
required + readOnly means the value will always be in the response but never needs to be in the request (including it back from a response is harmless)
neither: the property is read/write and optional, in at least some circumstances.