-
Notifications
You must be signed in to change notification settings - Fork 4
Database Repository Best Practices
This document should represent the best practices for writing/creating repositories inside of the backend that interface with the database layer.
NOTE - All examples will assume that the database table name is Agent
-
- Repositories should be placed into
common/db/repos/* - Models should be placed into
common/db/models/*
- Repositories should be placed into
-
- The interface file should be named
AgentRepository - The implementation should be named
AgentSqlRepository.
- The interface file should be named
-
-
Required annotations:
-
@Getter- Create getter methods for all variables:getId() -
@Setter- Create setter methods for all variables:setId("4819e35f-003b-4ad5-930f-6cd6a6102623"). If the class is immutable,@Setteris not required. -
@ToString- Generates atoString()method that will, by default, include all variable names. If you need to override this behavior, please check Lombok documentation on how to do so. -
@EqualsAndHashCode- Generates anequals()method, which requireshashcode()method, which is why it's included, which allows you to compare object equalities. This is very important when writing tests. -
@Builder- Generates aClassName.builder()which lets you use a builder pattern to create the object instead:- You may need
@SuperBuilderinstead if the class is extending from another base class which has@Builder.
Agent agent = Agent.builder().id("79e1d624-ab4f-4a28-9178-08f5a8bc4641").name("James Bond").build()
- You may need
-
@Jacksonized- This is required so a class can be de-serialized if it's ever converted from a JSON string into a Java object. It is best to always add this annotation.
-
-
NOTE - Do not use @Data. The annotation has too much scope, and it's better to just use the annotations you were going to apply.
-
Example file of a database object in Java:
@Getter @Setter @Builder @EqualsAndHashCode @ToString public class Agent { private String id; private String name; // Optional indicates that it can be null. private Optional<String> email; }
-
-
Follow these rules when it comes to repositories:
-
- The function for creating a new database object should always return void. Instead, the function should accept an input of the database object, and in the implementation, use the setters on the object to update any new values from the database. NOTE - You should make this specific behavior clear in the interface file for the specific creation method
- If you accept the object itself as an input (which you almost always should), make sure to leave a note about what fields are required to be set inside of the object.
- Below is an example of what it should look like:
// AgentRepository.java public interface AgentRepository { // ... /** * @note - The provided object's methods will be overridden with any returned * data from the database. * * @param agent - required fields: * <ul> * <li>id</li> * <li>type</li> * </ul> * optional fields: * <ul> * <li>email</li> * </ul> */ void createAgent(Agent agent); // ... }
-
-
Read functions should indicate the required values in order to find the row to read (e.g.
byIdorbyLeaderboardName). -
Read methods should return the database model object, but you should use a private method to standardize the way that the object is read from the ResultSet. Here is an example:
// in AgentSqlRepository.java @Component public class AgentSqlRepository implements AgentRepository { // ... private parseResultSetToAgent(final ResultSet rs) { return Agent.builder() .id(rs.getString("id")) .name(rs.getString("name")) .email(Optional.ofNullable(rs.getString("email")) .build(); } @Override public Agent getAgentById(final String id) { // ... if (rs.next()) { return parseResultSetToAgent(rs); } // ... } }
-
-
- For update functions, you should return a boolean depending on whether or not the operation was successful or not, but you should also refer to the Create operation and replace any values on the object if required. The function name should include what the function will use to search for the row to update (e.g.
byIdorbyUserId), but you should still pass in the entire Object if it may need to be updated. - Use Javadoc to indicate what fields will be updated, like in the example below:
// AgentRepository.java public interface AgentRepository { // ... /** * @note - The provided object's methods will be overridden with any returned * data from the database. * * @param agent - overridable fields: * <ul> * <li>name</li> * <li>email</li> * </ul> */ boolean updateAgentById(Agent agent); // ... }
- For update functions, you should return a boolean depending on whether or not the operation was successful or not, but you should also refer to the Create operation and replace any values on the object if required. The function name should include what the function will use to search for the row to update (e.g.
-
- For delete operations, you should return a boolean depending on whether or not the operation was successful or not. The entire Object is not required to be passed in, just what the function requires to find the row to delete.
// AgentRepository.java public interface AgentRepository { // ... boolean deleteAgentById(String id); // ... }
-
- There is a chance you may need to implement a method that doesn't fall neatly into the standards, such as searching for an external object ID (e.g. finding UserTags by using an userId). These are completely reasonable, and it's well within means to attempt a solution, which can get revised during pull requests.
-
- Home
- Setup
- Developer Commands
- Secrets
- Technical Docs
- Feature Docs
- CI Commands
- Contributing
- Codebloom ↗
- Codebloom - Swagger Documentation ↗
- Codebloom - Staging ↗
- Codebloom - Staging - Swagger Documentation ↗
- Codebloom - Local - Swagger Documentation ↗
- Codebloom - Local - Prometheus Metrics ↗
- Grafana Dashboard - Separate domain
- Public Taskboard (Read-only) ↗