-
Notifications
You must be signed in to change notification settings - Fork 0
Development Style Guide
This document outlines the coding standards and best practices for developing Spring Boot 3.5 web applications within our organisation. Adherence to these guidelines ensures code quality, maintainability, and consistency across all projects. This standard specifically addresses projects leveraging Spring Boot 3.5, JDK 17, Gradle for backend, and React 19 with Ant Design, Redux, and Axios for frontend development.
- Clarity & Readability: Code must be easy to read and understand. Prioritise clear, self-documenting code over clever, concise but obscure solutions.
- Consistency: Maintain consistent coding styles, naming conventions, and architectural patterns across the project.
- Modularity: Design components to be loosely coupled and highly cohesive, promoting reusability and easier testing.
- Testability: Write code that is inherently testable.
- Security by Design: Embed security considerations into every phase of development.
- Performance Awareness: Consider performance implications for critical sections of code and API endpoints.
The backend application (helix-server) follows a structured Gradle project layout. The core application logic resides within the uk.co.organisation.projectname package (adjusted to com.onixbyte.helix for your specific structure), with a clear separation of concerns into various sub-packages.
source/repos/helix/helix-server
├── build.gradle.kts // Main Gradle build script for the project
├── config // External configuration directory
│ ├── application-dev.yml // Application properties for development environment
│ └── application-prod.yml.example // Example production properties (to be copied and configured)
├── database // Database-related files
│ └── init.d // Initialisation scripts for the database
│ └── init-en_GB.sql // SQL script for database schema and initial data (British English locale)
├── gradle // Gradle wrapper and configuration files
│ ├── libs.versions.toml // Centralised dependency versions (Gradle Version Catalogs)
│ └── wrapper // Gradle wrapper files
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties // Project-specific Gradle properties
├── gradlew // Gradle wrapper executable (Linux/macOS)
├── gradlew.bat // Gradle wrapper executable (Windows)
├── settings.gradle.kts // Gradle settings for multi-project builds (if applicable)
└── src
├── main
│ ├── java
│ │ └── com/onixbyte/helix // Root package for the application
│ │ ├── HelixApplication.java // Main Spring Boot application entry point
│ │ ├── config // Spring configuration classes
│ │ ├── constant // Classes defining application-wide constants
│ │ ├── controller // REST API endpoints
│ │ ├── domain // Core domain models and related types
│ │ │ ├── common // Common domain objects/utility classes
│ │ │ ├── database // Database-specific domain objects (e.g., enums for DB types)
│ │ │ ├── entity // JPA/MyBatis entities representing database tables
│ │ │ ├── model // General purpose domain models not directly tied to DB entities
│ │ │ ├── view // DTOs specifically for read operations (e.g. data projections)
│ │ │ └── web // DTOs specifically for web request/response bodies
│ │ │ ├── request // Request DTOs
│ │ │ └── response // Response DTOs
│ │ ├── exception // Custom application-specific exceptions
│ │ ├── extension // Extension points or custom functionality
│ │ │ ├── jackson // Jackson serialisation/deserialisation extensions
│ │ │ └── redis // Redis-related extensions
│ │ │ └── serializer
│ │ ├── filter // Servlet filters or Spring Security filters
│ │ ├── manager // Business logic orchestrators, often coordinating multiple services or repositories
│ │ ├── processor // Generic processing components or business workflows
│ │ ├── properties // Classes for type-safe configuration properties (`@ConfigurationProperties`)
│ │ ├── repository // Data access layer (MyBatis Mapper interfaces)
│ │ ├── security // Spring Security specific components
│ │ │ ├── authentication // Custom authentication mechanisms
│ │ │ └── provider // Custom authentication providers
│ │ ├── service // Core business logic (transactional layer)
│ │ ├── utils // General utility classes
│ │ └── validation // Custom validation logic
│ │ └── group // Validation groups for different contexts (e.g., creation, update)
│ └── resources
│ ├── application.yml // Default application properties
│ └── repository // MyBatis XML mapper files
└── test
└── java
└── com/onixbyte/helix
└── HelixApplicationTests.java // Spring Boot integration tests
Key Observations and Specific Directives:
-
External
configDirectory: Environmental configurations (application-dev.yml,application-prod.yml.example) are managed in a top-levelconfigdirectory, separate fromsrc/main/resources. This promotes environment-specific property management, allowing different configurations to be mounted or linked at deployment time. -
Database Initialisation: The
database/init.ddirectory is reserved for SQL scripts, specifically for database schema initialisation (init-en_GB.sql), which is crucial for environment setup and CI/CD pipelines. This structure implies a "schema-first" or "code-driven schema evolution" approach. -
MyBatis Integration: The
src/main/java/.../repositorypackage contains MyBatis mapper interfaces, while the actual SQL definitions are located insrc/main/resources/repository/*.xmlfiles. This separation is key to maintaining clean code while leveraging MyBatis's powerful XML mapping capabilities. -
domainPackage Granularity:-
domain.entity: Reserved for classes that directly map to database tables (POJOs for MyBatis). -
domain.model: For more generic domain objects or aggregate roots not directly mapping one-to-one with a single table. -
domain.view: Specifically for DTOs used to present data in read-only scenarios (e.g., query results, report structures). -
domain.web.request/domain.web.response: Clearly segregated DTOs for incoming API requests and outgoing API responses, strictly adhering to the API contract and decoupling from internal domain entities.
-
-
managerandprocessorPackages: These packages suggest a layered architecture where "managers" orchestrate operations involving multiple services or repositories, while "processors" might handle specific aspects of a business process. It is imperative that the responsibilities of classes within these packages are clearly defined and documented to prevent an 'anemic domain model' or 'god object' anti-patterns. -
securityPackage: This sub-package houses custom Spring Security components beyond the initial configuration, such as custom authentication types and providers, indicating a tailored security implementation. -
propertiesPackage: This is well-placed for custom@ConfigurationPropertiesclasses, promoting type-safe access to application settings defined in YML files. -
extensionPackage: This is a flexible area for application-specific extensions, such as custom Jackson serialisers or Redis customisations. It should be used judiciously to prevent a 'miscellaneous' dumping ground.
- JDK 17 Features: Leverage modern JDK 17 features where appropriate to write cleaner and more efficient code (e.g., records, switch expressions, text blocks).
-
Naming Conventions:
- Classes: PascalCase (e.g.,
UserService,OrderController). - Methods: camelCase (e.g.,
getUserById,saveOrder). - Variables: camelCase (e.g.,
userName,statusCode). - Constants: SCREAMING_SNAKE_CASE (e.g.,
DEFAULT_PAGE_SIZE).
- Classes: PascalCase (e.g.,
-
Immutability: Favour immutability for domain objects and DTOs where possible, using
recordsor immutable classes to reduce side effects and improve thread safety. -
Optional: Use
Optional<T>to explicitly handle the potential absence of a value, avoidingNullPointerExceptions. - Streams API: Prefer the Java Streams API for collection processing, promoting functional and declarative programming.
-
Exception Handling:
- Use specific exceptions. Avoid catching generic
Exception. - For unrecoverable errors, throw runtime exceptions.
- For recoverable errors, define custom checked exceptions if business logic dictates.
- Utilise Spring's
@ControllerAdviceand@ExceptionHandlerfor centralised global exception handling and consistent API error responses.
- Use specific exceptions. Avoid catching generic
- Code Review: All backend code must undergo a thorough manual code review prior to merging, specifically using the GitFlow principle. IntelliJ IDEA's integrated code analysis tools should be used as a first pass.
-
Layered Architecture (MVC with Manager Layer): Our backend application adheres to a strict multi-layered architecture, ensuring a clear separation of concerns and promoting maintainability and testability. The layers and their responsibilities are as follows:
-
Controller Layer: Located in the
controllerpackage. Responsible for exposing RESTful APIs, handling HTTP requests, and mapping request parameters/bodies to service layer calls. Controllers should be thin, primarily focusing on input validation (using DTOs) and orchestrating calls to theServicelayer. -
Service Layer: Located in the
servicepackage. This layer encapsulates the core business logic. Services provide a consumable API to theControllerlayer, abstracting business processes and transaction management. Services coordinate calls to theManagerlayer to perform business operations. -
Manager Layer: Located in the
managerpackage. This layer provides atomic business operations that can be composed by theServicelayer. Managers often deal with more complex business logic that might involve interactions with multiple repositories or other external systems at a granular level. -
Repository Layer (MyBatis): Located in the
repositorypackage andsrc/main/resources/repository(for XML mappers). This layer is responsible for providing atomic database interaction operations.
-
Controller Layer: Located in the
-
Layer Communication Policy:
- Components within each layer may only invoke components in the layer directly beneath it.
- Cross-layer invocation (e.g., Controller calling Manager directly, Service calling Repository directly) is strictly forbidden.
- Upward invocation (e.g., Service calling Controller) is strictly forbidden.
-
Lateral invocation (e.g., Service A calling Service B for a different domain) should be carefully considered and typically indicates a need to refactor shared logic into a
Manageror a dedicatedServicefor that shared concern.
-
Configuration: Prefer
application.ymlfor configuration properties overapplication.propertiesfor better readability and hierarchical structure. Use@ConfigurationPropertiesfor type-safe configuration. -
Dependency Injection: Use constructor injection for all dependencies. Avoid
@Autowiredon fields or setters as it makes testing harder and hides dependencies. -
Services: Annotate business logic classes with
@Service. Keep services thin and focused on orchestrating domain operations, which often involve interacting with MyBatis repositories for data access, rather than containing direct data access logic. -
Repositories (MyBatis):
-
MyBatis is used for database operations. Repository (mapper) interfaces in the
repositorypackage define the data access contract. - The corresponding SQL definitions are managed in XML mapper files located in
src/main/resources/repository. -
MyBatis Method Naming Conventions:
- For querying a list of data: Methods must start with
selectListBy, immediately followed by criteria (e.g.,selectListByUserId,selectListByDepartmentIdAndStatus). These methods must also include aPageRequestparameter for pagination. - For querying a single data record: Methods must start with
selectOne(e.g.,selectOneById,selectOneByUsername). - For saving new data: Methods must be named
save(e.g.,saveUser,saveDepartment). - For updating existing data: Methods must be named
update(e.g.,updateUser,updateDepartmentById). - For deleting data: Methods must start with
deleteBy, clearly specifying the criteria for deletion (e.g.,deleteById,deleteByUserId).
- For querying a list of data: Methods must start with
-
MyBatis is used for database operations. Repository (mapper) interfaces in the
-
Controllers:
- Annotate REST controllers with
@RestController. - Map HTTP methods (GET, POST, PUT, DELETE) to appropriate controller methods using
@GetMapping,@PostMapping, etc. - Ensure request and response payloads are well-defined (DTOs) and documented.
- Controllers should primarily handle HTTP request/response mapping and delegate actual business logic to service layers.
- Annotate REST controllers with
-
DTOs (Data Transfer Objects): Define separate DTOs for request and response bodies to decouple internal domain models from API contracts. Use validation annotations (e.g.,
@Valid,@NotNull,@Size) on DTOs. -
Logging (Slf4j & Logback):
- Use
org.slf4j.Loggerfor all logging within the application. - Configure
logback-spring.xmlfor log levels, appenders, and output formats. - Log messages should be descriptive and provide sufficient context. Avoid logging sensitive information.
- Use parameterized logging to improve performance and prevent string concatenation overhead (e.g.,
logger.debug("Processing user: {}", userId);). - Standard logging levels:
ERROR,WARN,INFO,DEBUG,TRACE.
- Use
-
Build File:
build.gradlemust be well-organised, with clear sections for plugins, dependencies, and tasks. -
Dependency Versions: Centralise dependency versions in the
build.gradlefile orgradle.propertieswhere possible to ensure consistency. - Plugin Management: Explicitly declare Gradle plugins and their versions.
- Avoid Unnecessary Dependencies: Only include dependencies that are actively used by the project. Regularly review and clean up unused dependencies.
- Mandatory Use: Spring Security is mandatory for all Spring Boot web applications.
-
Authentication & Authorisation: Configure authentication mechanisms (e.g., OAuth 2.0, JWT, session-based) and authorisation rules within
SecurityConfig.java. - CSRF Protection: Ensure CSRF protection is enabled for state-changing operations unless a valid reason for disabling it exists (e.g., stateless API where other mechanisms are in place).
- CORS: Properly configure Cross-Origin Resource Sharing (CORS) based on frontend deployment requirements.
- Third-Party Identity Providers: When integrating with third-party identity providers (e.g., Microsoft Entra ID), follow best practices for secure token handling and user provisioning. Sensitive credentials must be managed securely (e.g., environment variables, Vault).
- Input Validation: Always validate all user inputs on the server-side to prevent common vulnerabilities like SQL injection, XSS, etc.
- Content Security Policy (CSP): Consider implementing a strong CSP for the frontend to mitigate XSS attacks.
-
RESTful Principles: Adhere to RESTful principles:
- Resources: Model data as resources identifiable by URIs.
- HTTP Methods: Use standard HTTP methods (GET for retrieval, POST for creation, PUT for full update, PATCH for partial update, DELETE for removal) appropriately.
- Statelessness: APIs should be stateless; each request from a client to a server must contain all the information needed to understand the request.
- Hypermedia (HATEOAS): While not strictly mandatory for all internal APIs, consider HATEOAS for public-facing APIs for discoverability.
-
URIs:
- Use plural nouns for collection resources (e.g.,
/users,/products). - Use hyphens for readability in URIs (e.g.,
/user-accounts). - Avoid verbs in URIs (e.g., instead of
/getAllUsers, use/users).
- Use plural nouns for collection resources (e.g.,
-
Status Codes: Use appropriate HTTP status codes to indicate the outcome of an API request (e.g.,
200 OK,201 Created,204 No Content,400 Bad Request,401 Unauthorized,403 Forbidden,404 Not Found,500 Internal Server Error). - Response Formats: JSON is the preferred response format. Ensure consistent JSON structure for both success and error responses.
-
Versioning: Currently, versioning is not implemented. Should it become necessary, the preferred approach will be to use a custom request header, such as
X-Endpoint-Version, to indicate the desired API version. This allows for flexible version management without polluting the URI.- Example:
GET /api/userswith headerX-Endpoint-Version: 1.0
- Example:
The frontend application follows a standard modern React project structure, leveraging Vite for development. The src directory organises code by concern, promoting maintainability and scalability.
├── eslint.config.js // ESLint configuration for code quality
├── index.html // Main HTML entry point
├── package.json // Project metadata and dependencies
├── pnpm-lock.yaml // pnpm lock file for deterministic dependencies
├── public // Static assets served directly (e.g., global images)
│ └── vite.svg
├── src // Main application source code
│ ├── api // Low-level API client definitions (e.g., Axios instances, raw request functions)
│ │ ├── auth // Authentication-specific API calls
│ │ │ └── index.ts
│ │ └── index.ts // Centralised API export
│ ├── assets // Static assets used within components (e.g., component-specific images, icons)
│ │ ├── dingtalk.svg
│ │ ├── microsoft.svg
│ │ ├── password.svg
│ │ ├── react.svg
│ │ └── wecom.svg
│ ├── components // Reusable UI components (display logic, no business logic)
│ │ └── protected-route // Example: A route guard component
│ │ └── index.tsx
│ ├── config // Application-wide configuration values
│ │ ├── index.ts
│ │ └── msal-config // Microsoft Entra ID (MSAL) specific configuration
│ │ └── index.ts
│ ├── hooks // Custom React Hooks for reusable logic
│ ├── index.css // Global CSS styles
│ ├── layouts // Page layouts (e.g., header, sidebar, footer structures)
│ ├── main.tsx // Main React application entry point (root component rendering)
│ ├── page // Route-specific components/views (pages)
│ │ ├── login // Login page components
│ │ │ ├── index.tsx
│ │ │ ├── msal-login // MSAL-specific login component
│ │ │ │ └── index.tsx
│ │ │ └── username-and-password // Username/password login component
│ │ │ └── index.tsx
│ │ └── register // Registration page components
│ │ └── index.tsx
│ ├── service // Business logic layer, orchestrating API calls and state management
│ │ ├── auth // Authentication-related business logic
│ │ │ ├── index.ts
│ │ │ └── msal // MSAL-specific authentication logic
│ │ │ └── index.ts
│ │ └── web-client // Generic web client utilities or shared service logic
│ │ └── index.ts
│ ├── store // Redux store and associated slices
│ │ ├── auth-slice // Redux slice for authentication state management
│ │ │ └── index.ts
│ │ └── index.ts // Redux store configuration
│ ├── types // TypeScript type definitions
│ │ └── index.ts
│ └── vite-env.d.ts // Vite-specific environment type declarations
├── tsconfig.app.json // TypeScript configuration for application code
├── tsconfig.json // Base TypeScript configuration
├── tsconfig.node.json // TypeScript configuration for Node.js environments (e.g., Vite config)
└── vite.config.ts // Vite build configuration
Key Observations and Specific Directives:
-
src/apivs.src/service: This structure suggests a clear separation:-
src/api: Should contain raw, low-level functions for making HTTP requests (e.g., Axios calls) without much business logic. Its primary role is to map to backend API endpoints and handle basic request/response formatting for the transport layer. -
src/service: Should encapsulate higher-level business logic. Components in theservicelayer will utilise functions from theapilayer, combine data from multiple API calls, implement retry logic, and handle data transformation before providing it topagecomponents or Reduxstoreslices.
-
-
src/assetsvs.public:-
publicdirectory: For static assets that are served directly by the web server at/, without being processed by Vite's build pipeline (e.g.,vite.svg). Ideal for small, global assets orrobots.txt. -
src/assets: For images, icons, or other media assets that are imported into React components and handled by Vite's asset bundling.
-
-
src/componentsvs.src/page:-
src/components: Dedicated to small, reusable UI components that are generally "dumb" – they receive data via props and emit events. They should not contain route-specific logic, significant business logic, or directly manage global state (though they can dispatch actions). -
src/page: Contains "smart" components that represent specific application views or routes. Pages orchestrate components, fetch data (often viaserviceorstore), and handle route-specific logic.
-
-
src/config: Centralises application-wide configurations, includingmsal-config, which is essential for managing Microsoft Entra ID integration parameters. -
src/store: Adheres to the Redux "slices" pattern for managing global application state, withauth-slicebeing a clear example of separating authentication-related state. -
src/hooks: Explicitly defined for custom React Hooks, promoting the reuse of stateful logic across components. -
src/layouts: Contains components for structuring overall page layouts, ensuring a consistent application shell across different views. -
TypeScript Configuration: Multiple
tsconfig.jsonfiles (tsconfig.json,tsconfig.app.json,tsconfig.node.json) are used to manage TypeScript settings for different compilation contexts (e.g., application code, Vite configuration files). This is a good practice for modern TypeScript projects.
- Functional Components & Hooks: Prefer functional components with React Hooks over class components for new development.
-
Props:
- Define
interfaceortypefor component props to ensure type safety. - Destructure props at the component's entry point for clarity.
- Define
-
State Management (Redux):
- Use Redux Toolkit for efficient and boilerplate-reducing Redux development.
- Organise Redux logic into "slices" (reducers, actions, and selectors for a specific feature) using
createSlice. - Follow the "ducks" pattern or "slices" approach for co-locating Redux logic.
- Component Composition: Break down complex UI into smaller, reusable, and single-responsibility components.
-
Ant Design:
- Leverage Ant Design components for a consistent UI/UX.
- Customise Ant Design themes and styles consistently across the application using Less variables or CSS-in-JS solutions if preferred.
- Accessibility: Design and implement components with web accessibility (a11y) in mind from the outset.
- Axios Instances: Create a centralised Axios instance for API calls to apply common configurations (base URL, headers, interceptors).
-
Interceptors: Use Axios interceptors for:
- Adding authentication tokens to outgoing requests.
- Handling global error responses (e.g., showing notifications for
401 Unauthorized). - Logging requests/responses in development.
- Error Handling: Centralise API error handling within Axios interceptors or custom utility functions to provide consistent user feedback.
-
Strictness: Utilise pnpm's strict dependency management to ensure a more deterministic
node_modulesstructure and efficient disk space usage. - Workspaces: If using a monorepo approach, configure pnpm workspaces for streamlined dependency management across multiple frontend packages.
-
Audit: Regularly audit frontend dependencies for known vulnerabilities using
pnpm audit.
-
Javadoc (Backend):
- All public classes, methods, and significant fields in backend Java code must have comprehensive Javadoc comments.
- Javadoc should explain the purpose, parameters (
@param), return values (@return), and exceptions thrown (@throws).
-
Frontend Comments:
- Use clear and concise comments where the code's intent is not immediately obvious.
- Components, utility functions, and Redux slices should have introductory comments explaining their purpose.
- Architecture Documentation: An overarching architecture document for the project is mandatory and currently under development. This document will describe the system's high-level design, key components, data flows, and architectural choices.
-
README.md: Each project (or monorepo root) should have a
README.mdfile providing:- Project title and description.
- Setup and installation instructions.
- Build and run commands.
- Testing instructions.
- Deployment guidelines.
- Links to relevant documentation (e.g., Architecture Document).
-
Dockerfiles: Provide a
Dockerfilefor the backend application, enabling containerised deployment. -
Lightweight Images: Aim for lightweight Docker images by using appropriate base images (e.g.,
eclipse-temurin:17-jdk-focal,node:19-alpinefor frontend) and multi-stage builds. - Configuration: Ensure environment-specific configurations (e.g., database connection strings, external service URLs) are managed through environment variables injected into the Docker container.
-
Health Checks: Implement Spring Boot Actuator endpoints (e.g.,
/actuator/health) for readiness and liveness probes in container orchestration environments. -
Logging: Configure containerised logging to output to
stdoutandstderrfor easy collection by log aggregation systems.
-
GitFlow Workflow: The GitFlow branching model is to be used for version control, comprising
main,develop,feature,release, andhotfixbranches.-
main: Production-ready code. Onlyreleaseandhotfixbranches are merged intomain. -
develop: Integration branch for upcoming features. -
feature/*: Branches for new features, branched fromdevelop. -
release/*: Branches for preparing a new production release, branched fromdevelop. -
hotfix/*: Branches for urgent production bug fixes, branched frommain.
-
- Pull Requests (PRs): All code changes (except direct pushes to feature branches) must be submitted via Pull Requests.
-
Code Review:
- Every Pull Request must be reviewed by at least one other developer.
- Reviewers are responsible for checking adherence to this code standard, code quality, logical correctness, and test coverage.
- IntelliJ IDEA's integrated code analysis tools should be run locally before creating a PR.
-
Commit Messages: Write clear, concise, and descriptive commit messages that explain what the change is and why it was made. Follow a conventional commit format if possible (e.g.,
feat: add new user registration endpoint).