Files
Virtnet.bond/.cursor/rules/phoenix.mdc

270 lines
18 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
description: This rule outlines the best practices and coding standards for developing Elixir applications with the Phoenix framework, covering code organization, performance, security, testing, and common pitfalls.
globs: *.ex,*.exs,*.eex,*.leex,*.sface
---
- # Phoenix Framework Best Practices
This document outlines general best practices for developing Elixir applications with the Phoenix Framework. It aims to promote maintainable, scalable, and performant codebases.
- ## 1. Code Organization and Structure
- ### 1.1. Directory Structure Best Practices
- **`lib/`**: Contains the core application logic, including contexts, schemas, and supporting modules.
- **`lib/<app_name>_web/`**: Holds the web-related components, such as controllers, views, templates, channels, and live views.
- **`lib/<app_name>/`**: Contains your application’s core domain logic. Favor placing most of your code here.
- **`test/`**: Contains all test-related files, including unit, integration, and acceptance tests.
- **`priv/repo/migrations/`**: Stores database migration files.
- **`config/`**: Contains configuration files for different environments (dev, test, prod).
- **`assets/`**: Contains static assets like JavaScript, CSS, and images. Managed by tools like esbuild or Webpack.
- **`deps/`**: Automatically generated folder containing project dependencies.
- **`rel/`**: Used for building releases with distillery or mix release. Contains configuration for releases.
- ### 1.2. File Naming Conventions
- Use `snake_case` for file names (e.g., `user_controller.ex`, `user_schema.ex`).
- Match the file name to the module name (e.g., `UserController` module should be in `user_controller.ex`).
- For LiveView components, use `_live` suffix (e.g., `user_live.ex`).
- For schema files, use `_schema` suffix(e.g. `user_schema.ex`).
- ### 1.3. Module Organization
- Organize modules into contexts, representing logical domains within the application (e.g., `Accounts`, `Blog`, `Payments`).
- Keep modules small and focused, adhering to the Single Responsibility Principle (SRP).
- Use namespaces to group related modules (e.g., `MyApp.Accounts.User`, `MyApp.Blog.Post`).
- Favor explicit dependencies between modules to promote clarity and reduce coupling.
- ### 1.4. Component Architecture (LiveView)
- Break down complex LiveView pages into smaller, reusable components.
- Use functional components (HEEx templates with function definitions) for simple UI elements.
- Use LiveComponent modules for more complex, stateful components with event handling.
- Organize components into directories based on their purpose or domain.
- Define attributes and slots for LiveView components to ensure type safety.
- Utilize PubSub for communication between LiveView components.
- ### 1.5. Code Splitting Strategies
- Separate concerns by using contexts. Controllers delegate to contexts, which handle the business logic, and contexts rely on schemas, which handle the data validations and structure.
- Utilize umbrella projects to divide a large application into smaller, independent applications.
- Consider code splitting at the LiveView level, using separate LiveView modules for different sections of a page.
- Use dynamic imports for JavaScript modules to load code on demand.
- ## 2. Common Patterns and Anti-patterns
- ### 2.1. Design Patterns
- **MVC (Model-View-Controller)**: The core architectural pattern in Phoenix. Ensure proper separation of concerns between models (schemas), views (templates), and controllers.
- **Context Pattern**: Encapsulate business logic and data access within contexts to provide a clear API for controllers and other parts of the application.
- **Repository Pattern**: Abstract away database interactions behind a repository interface to allow for easier testing and potential database changes. While contexts often serve this purpose in Phoenix, a dedicated repository layer can be useful for very complex data access logic.
- **PubSub**: Use `Phoenix.PubSub` for real-time communication between different parts of the application (e.g., LiveView components, channels).
- **Ecto Changesets**: Employ Ecto changesets for data validation and sanitization before persisting to the database.
- ### 2.2. Recommended Approaches for Common Tasks
- **Authentication**: Use a library like `Pow` or `Ueberauth` for handling user authentication and authorization.
- **Authorization**: Implement role-based access control (RBAC) or attribute-based access control (ABAC) using libraries like `Pomegranate` or custom logic.
- **Form Handling**: Use `Phoenix.HTML.Form` helpers and Ecto changesets for building and validating forms.
- **Real-time Updates**: Leverage Phoenix Channels or LiveView for real-time updates and interactive features.
- **Background Jobs**: Use `Oban` for reliable background job processing.
- ### 2.3. Anti-patterns and Code Smells
- **Fat Controllers**: Avoid putting too much logic in controllers. Delegate to contexts or services.
- **Direct Repo Access in Controllers**: Do not directly call `Repo` functions in controllers. Use contexts.
- **Ignoring Changeset Errors**: Always handle changeset errors and display appropriate messages to the user.
- **Over-Complicated Queries**: Keep Ecto queries simple and composable. Use views or functions in schema modules to build more complex queries.
- **Unnecessary Global State**: Minimize the use of global state. Prefer passing data explicitly between functions and components.
- ### 2.4. State Management Best Practices (LiveView)
- Store only the minimal required state in LiveView's `assigns`.
- Use `handle_params` to load data based on URL parameters.
- Employ `handle_info` for handling asynchronous events and updates.
- Implement proper state cleanup in `mount` and `terminate` callbacks.
- Consider using a global state management library like `global` only when truly necessary for cross-component communication.
- ### 2.5. Error Handling Patterns
- Use pattern matching to handle different error scenarios.
- Raise exceptions only for truly exceptional cases. For expected errors, return `{:error, reason}` tuples.
- Implement error logging and monitoring using tools like `Sentry` or `Bugsnag`.
- Use `try...rescue` blocks for handling potential exceptions during external API calls or file operations.
- Define custom error types using atoms or structs to provide more context for error handling.
- ## 3. Performance Considerations
- ### 3.1. Optimization Techniques
- **Database Queries**: Optimize Ecto queries by using indexes, preloading associations, and avoiding N+1 queries.
- **Caching**: Implement caching for frequently accessed data using `ETS` tables or a dedicated caching library like `Cachex`.
- **Concurrency**: Leverage Elixir's concurrency features (e.g., `Task`, `GenServer`) to handle concurrent requests and background tasks efficiently.
- **Code Profiling**: Use tools like `Erlang's observer` or `fprof` to identify performance bottlenecks in your code.
- **Connection Pooling**: Ensure proper database connection pooling to minimize connection overhead.
- ### 3.2. Memory Management
- **Avoid Memory Leaks**: Be mindful of memory leaks, especially in long-running processes like `GenServers`.
- **Use Streams**: Employ Elixir streams for processing large datasets in a memory-efficient manner.
- **Garbage Collection**: Understand Erlang's garbage collection behavior and optimize code to minimize garbage collection overhead.
- ### 3.3. Rendering Optimization (LiveView)
- **Minimize DOM Updates**: Reduce the number of DOM updates in LiveView by using `phx-update` attributes and smart diffing.
- **Use Static Content**: Serve static content (e.g., images, CSS, JavaScript) directly from the web server without involving LiveView.
- **Optimize Templates**: Optimize HEEx templates for efficient rendering.
- ### 3.4. Bundle Size Optimization
- **Code Splitting**: Use code splitting to reduce the initial bundle size and load code on demand.
- **Tree Shaking**: Enable tree shaking in esbuild or Webpack to remove unused code from the bundle.
- **Minification**: Minify CSS and JavaScript files to reduce their size.
- **Image Optimization**: Optimize images for web use by compressing them and using appropriate formats.
- ### 3.5. Lazy Loading
- **Lazy Load Images**: Implement lazy loading for images to improve initial page load time.
- **Lazy Load Components**: Use dynamic imports or conditional rendering to lazy load LiveView components.
- ## 4. Security Best Practices
- ### 4.1. Common Vulnerabilities and Prevention
- **Cross-Site Scripting (XSS)**: Sanitize user input and use proper escaping in templates to prevent XSS attacks. Phoenix's HEEx templates automatically escape variables, but be careful when using `raw/1` or `safe/1`.
- **Cross-Site Request Forgery (CSRF)**: Protect against CSRF attacks by using Phoenix's built-in CSRF protection mechanisms. Include the CSRF token in forms and AJAX requests.
- **SQL Injection**: Use Ecto's parameterized queries to prevent SQL injection vulnerabilities.
- **Authentication and Authorization Issues**: Implement robust authentication and authorization mechanisms to protect sensitive data and functionality.
- ### 4.2. Input Validation
- **Use Ecto Changesets**: Always validate user input using Ecto changesets to ensure data integrity and prevent malicious input.
- **Whitelist Input**: Define a whitelist of allowed input values instead of a blacklist of disallowed values.
- **Sanitize Input**: Sanitize user input to remove potentially harmful characters or code.
- ### 4.3. Authentication and Authorization
- **Use Strong Passwords**: Enforce strong password policies and use proper hashing algorithms (e.g., `bcrypt`) to store passwords securely.
- **Implement Multi-Factor Authentication (MFA)**: Add an extra layer of security by requiring users to authenticate using multiple factors.
- **Use JWT (JSON Web Tokens)**: Use JWT for secure API authentication and authorization.
- **Implement Role-Based Access Control (RBAC)**: Define roles and permissions to control access to different parts of the application.
- ### 4.4. Data Protection
- **Encrypt Sensitive Data**: Encrypt sensitive data at rest and in transit using appropriate encryption algorithms.
- **Use HTTPS**: Always use HTTPS to encrypt communication between the client and the server.
- **Protect API Keys**: Store API keys securely and restrict access to them.
- ### 4.5. Secure API Communication
- **Use HTTPS**: Enforce HTTPS for all API communication.
- **Validate API Requests**: Validate API requests to prevent malicious input and unauthorized access.
- **Rate Limiting**: Implement rate limiting to prevent abuse and denial-of-service attacks.
- **API Versioning**: Use API versioning to ensure backward compatibility and allow for future API changes.
- ## 5. Testing Approaches
- ### 5.1. Unit Testing
- **Test Contexts and Schemas**: Write unit tests for contexts and schemas to ensure that business logic and data validation work as expected.
- **Mock External Dependencies**: Use mocking libraries like `Mock` or `Meck` to isolate units under test and avoid dependencies on external services.
- **Test Edge Cases**: Test edge cases and boundary conditions to ensure that code handles unexpected input correctly.
- ### 5.2. Integration Testing
- **Test Controllers and Channels**: Write integration tests for controllers and channels to ensure that they interact correctly with contexts and other parts of the application.
- **Use a Test Database**: Use a dedicated test database to avoid affecting the production database.
- **Verify Database Interactions**: Verify that database interactions are performed correctly by inspecting the database state after running tests.
- ### 5.3. End-to-End Testing
- **Use Wallaby or Cypress**: Use end-to-end testing frameworks like Wallaby or Cypress to simulate user interactions and verify that the application works as expected from the user's perspective.
- **Test Critical User Flows**: Test critical user flows to ensure that the most important features of the application are working correctly.
- ### 5.4. Test Organization
- **Organize Tests by Module**: Organize tests into directories that mirror the application's module structure.
- **Use Descriptive Test Names**: Use descriptive test names to clearly indicate what each test is verifying.
- **Keep Tests Small and Focused**: Keep tests small and focused to make them easier to understand and maintain.
- ### 5.5. Mocking and Stubbing
- **Use Mock Libraries**: Use mocking libraries like `Mock` or `Meck` to create mock objects and stub function calls.
- **Avoid Over-Mocking**: Avoid over-mocking, as it can make tests less realistic and harder to maintain.
- **Use Stubs for External Dependencies**: Use stubs to replace external dependencies with simplified versions that are easier to control during testing.
- ## 6. Common Pitfalls and Gotchas
- ### 6.1. Frequent Mistakes
- **Not Using Contexts**: Directly accessing the Repo in controllers or other modules, bypassing the business logic encapsulation provided by contexts.
- **Ignoring Changeset Validation Errors**: Neglecting to handle and display changeset validation errors to the user, leading to confusing behavior.
- **N+1 Queries**: Failing to preload associations in Ecto queries, resulting in performance bottlenecks.
- **Over-reliance on Global State**: Using global state when it's not necessary, making code harder to reason about and test.
- ### 6.2. Edge Cases
- **Handling Timezones**: Dealing with timezones correctly, especially when storing and displaying dates and times to users in different locations.
- **Concurrency Issues**: Avoiding race conditions and other concurrency issues when using Elixir's concurrency features.
- **Large File Uploads**: Handling large file uploads efficiently and securely.
- ### 6.3. Version-Specific Issues
- **LiveView Updates**: Staying up-to-date with LiveView updates and understanding potential breaking changes.
- **Ecto Version Compatibility**: Ensuring compatibility between Ecto and other dependencies.
- ### 6.4. Compatibility Concerns
- **JavaScript Framework Integration**: Integrating Phoenix with JavaScript frameworks like React or Vue.js can introduce compatibility issues.
- **Database Driver Compatibility**: Ensuring compatibility between Ecto and the chosen database driver.
- ### 6.5. Debugging Strategies
- **Use IEx.pry**: Use `IEx.pry` to pause execution and inspect variables.
- **Enable Debug Logging**: Enable debug logging to get more information about what's happening in the application.
- **Use Remote Debugging**: Use remote debugging tools to debug applications running in production environments.
- **Analyze Logs**: Analyze logs to identify errors and performance issues.
- ## 7. Tooling and Environment
- ### 7.1. Recommended Development Tools
- **Visual Studio Code**: VS Code with the ElixirLS extension provides excellent Elixir support, including code completion, linting, and debugging.
- **IntelliJ IDEA**: IntelliJ IDEA with the Elixir plugin also provides excellent Elixir support.
- **Mix**: Elixir's build tool, used for creating, compiling, testing, and managing dependencies.
- **IEx**: Elixir's interactive shell, used for exploring code and debugging.
- **Erlang Observer**: Erlang's GUI tool for monitoring and debugging Erlang/Elixir applications.
- ### 7.2. Build Configuration
- **Use `mix.exs`**: Use `mix.exs` to manage project dependencies, compilation settings, and other build configurations.
- **Define Environments**: Define separate environments for development, testing, and production.
- **Use Configuration Files**: Use configuration files (`config/config.exs`, `config/dev.exs`, `config/test.exs`, `config/prod.exs`) to configure the application for different environments.
- **Secrets Management**: Use environment variables or dedicated secrets management tools to store sensitive information like API keys and database passwords.
- ### 7.3. Linting and Formatting
- **Use `mix format`**: Use `mix format` to automatically format Elixir code according to a consistent style.
- **Use Credo**: Use Credo to analyze Elixir code for style and potential issues.
- **Configure Editor**: Configure the editor to automatically format code on save.
- ### 7.4. Deployment
- **Use Mix Release**: Use `mix release` to build and deploy Elixir applications. Distillery is deprecated.
- **Use Docker**: Use Docker to containerize Elixir applications for easy deployment and scaling.
- **Deploy to Cloud Platforms**: Deploy Elixir applications to cloud platforms like Heroku, AWS, Google Cloud, or Azure.
- **Use a Process Manager**: Use a process manager like `systemd` or `pm2` to manage Elixir application processes.
- ### 7.5. CI/CD Integration
- **Use GitHub Actions, GitLab CI, or CircleCI**: Use CI/CD platforms to automate the build, test, and deployment processes.
- **Run Tests on CI**: Run tests on CI to ensure that code changes don't introduce regressions.
- **Automate Deployments**: Automate deployments to production environments after successful tests.
- ## Additional Resources
- [Phoenix Framework Documentation](https://www.phoenixframework.org/docs)
- [Elixir Language Documentation](https://elixir-lang.org/docs.html)
- [Ecto Documentation](https://hexdocs.pm/ecto/Ecto.html)
- [Phoenix LiveView Documentation](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html)
- [Oban Documentation](https://github.com/sorentwo/oban)
- [Credo Documentation](https://hexdocs.pm/credo/Credo.html)