# Technical Requirements & Implementation Specification ## 1. Feature Overview **Feature Name:** [Feature Name] **Ticket ID:** [JIRA-XXX] **Priority:** [High/Medium/Low] **Estimated Complexity:** [Simple/Medium/Complex] **Target Rails Version:** [7.x/8.x] ### Business Context [Brief description of the business problem this feature solves] ### Technical Summary [High-level technical approach - 2-3 sentences] --- ## 2. Architecture & Design Patterns ### Architectural Approach - [ ] **Pattern Selection:** - [ ] Service Object Pattern (for complex business logic) - [ ] Form Object Pattern (for complex validations) - [ ] Query Object Pattern (for complex queries) - [ ] Decorator/Presenter Pattern (for view logic) - [ ] Repository Pattern (for data access abstraction) - [ ] Observer Pattern (for event-driven features) - [ ] Strategy Pattern (for interchangeable algorithms) - [ ] Factory Pattern (for object creation) ### Design Decisions ``` Decision 1: [e.g., Use Service Objects for multi-step operations] Rationale: [Why this approach?] Alternative Considered: [What else was considered?] Trade-offs: [Performance, complexity, maintainability] ``` ### Module Organization ``` app/ ├── services/ │ └── [feature_name]/ │ ├── creator.rb │ ├── updater.rb │ └── destroyer.rb ├── queries/ │ └── [feature_name]/ ├── forms/ │ └── [feature_name]/ ├── decorators/ │ └── [feature_name]/ └── policies/ └── [feature_name]_policy.rb ``` --- ## 3. Database Schema Changes ### New Models ```ruby # app/models/[model_name].rb class ModelName < ApplicationRecord # Associations belongs_to :user has_many :related_models, dependent: :destroy # Validations validates :attribute, presence: true, uniqueness: { scope: :user_id } # Scopes scope :active, -> { where(status: 'active') } # Enums enum status: { draft: 0, published: 1, archived: 2 } # Callbacks (use sparingly) after_create :schedule_processing end ``` ### Migrations Required ```ruby # db/migrate/[timestamp]_create_table_name.rb class CreateTableName < ActiveRecord::Migration[7.1] def change create_table :table_names do |t| t.references :user, null: false, foreign_key: true, index: true t.string :name, null: false t.text :description t.integer :status, default: 0, null: false t.jsonb :metadata, default: {} t.datetime :published_at t.timestamps end add_index :table_names, [:user_id, :status] add_index :table_names, :published_at add_index :table_names, :metadata, using: :gin end end ``` ### Index Strategy - [ ] **Primary Indexes:** - [ ] `[table_name]` on `[column]` - For [specific query pattern] - [ ] Composite index: `[col1, col2]` - For [query optimization] - [ ] **Performance Indexes:** - [ ] GIN index on JSONB columns for metadata queries - [ ] Partial index for frequently filtered subsets - [ ] Unique index for business constraints - [ ] **Foreign Key Indexes:** - [ ] All foreign keys indexed (Rails doesn't do this automatically) ### Data Migration Strategy ```ruby # For existing data transformation class MigrateExistingData < ActiveRecord::Migration[7.1] def up # Use find_each for large datasets Model.find_each do |record| record.update_column(:new_field, transform_value(record.old_field)) end end def down # Reversible migration end private def transform_value(value) # Transformation logic end end ``` ### Database Performance Considerations - [ ] Estimate table size after 1 year: [X million rows] - [ ] Partitioning strategy (if needed): [Date-based/Range-based] - [ ] Archive strategy for old data: [After X months] - [ ] Expected query patterns documented - [ ] EXPLAIN ANALYZE results for critical queries --- ## 4. API Endpoints Specification ### RESTful Endpoints ```ruby # config/routes.rb namespace :api do namespace :v1 do resources :resource_name, only: [:index, :show, :create, :update, :destroy] do member do post :custom_action end collection do get :search end end end end ``` ### Endpoint Details #### GET /api/v1/resource_name **Purpose:** List resources with filtering and pagination **Authentication:** Required (JWT/Session) **Authorization:** User must have `read:resource` permission **Request Parameters:** ```json { "page": 1, "per_page": 25, "filter[status]": "active", "filter[created_after]": "2024-01-01", "sort": "-created_at", "include": "related_resource" } ``` **Response (200 OK):** ```json { "data": [ { "id": "123", "type": "resource_name", "attributes": { "name": "Resource Name", "status": "active", "created_at": "2024-01-01T00:00:00Z" }, "relationships": { "related_resource": { "data": { "id": "456", "type": "related_resource" } } } } ], "meta": { "total_count": 100, "page": 1, "per_page": 25, "total_pages": 4 }, "links": { "self": "https://api.example.com/v1/resource_name?page=1", "next": "https://api.example.com/v1/resource_name?page=2", "last": "https://api.example.com/v1/resource_name?page=4" } } ``` **Error Responses:** - `401 Unauthorized` - Invalid or missing authentication - `403 Forbidden` - Insufficient permissions - `422 Unprocessable Entity` - Invalid parameters - `429 Too Many Requests` - Rate limit exceeded #### POST /api/v1/resource_name **Purpose:** Create new resource **Authentication:** Required **Authorization:** User must have `create:resource` permission **Request Body:** ```json { "data": { "type": "resource_name", "attributes": { "name": "Resource Name", "description": "Description" }, "relationships": { "related_resource": { "data": { "id": "456", "type": "related_resource" } } } } } ``` **Response (201 Created):** ```json { "data": { "id": "123", "type": "resource_name", "attributes": { ... } } } ``` ### API Versioning Strategy - [ ] Version in URL path: `/api/v1/` - [ ] Deprecation timeline: [X months notice] - [ ] Backward compatibility requirements - [ ] API documentation tool: [Swagger/RSpec API Docs] ### Rate Limiting ```ruby # Rate limit strategy - Authenticated users: 1000 requests/hour - Anonymous users: 100 requests/hour - Specific endpoints: [Custom limits] - Implementation: Rack::Attack / Redis-based ``` --- ## 5. Service Objects & Business Logic ### Service Object Structure ```ruby # app/services/feature_name/creator.rb module FeatureName class Creator include Dry::Monads[:result, :do] def initialize(user:, params:) @user = user @params = params end def call resource = yield build_resource yield validate_business_rules(resource) yield persist_resource(resource) yield trigger_side_effects(resource) Success(resource) end private attr_reader :user, :params def build_resource resource = user.resources.build(resource_params) Success(resource) rescue StandardError => e Failure([:build_failed, e.message]) end def validate_business_rules(resource) return Success(resource) if business_rules_valid?(resource) Failure([:business_rule_violation, resource.errors]) end def persist_resource(resource) return Success(resource) if resource.save Failure([:persistence_failed, resource.errors]) end def trigger_side_effects(resource) # Queue background jobs NotificationJob.perform_later(resource.id) AnalyticsJob.perform_later(resource.id) Success(resource) end def resource_params params.permit(:name, :description, :status) end def business_rules_valid?(resource) # Complex business logic return false if user.resources.count >= user.resource_limit return false if duplicate_resource_exists?(resource) true end def duplicate_resource_exists?(resource) user.resources.where(name: resource.name).exists? end end end ``` ### Service Object Organization - [ ] **Command Pattern Services:** - [ ] `Creator` - Handle resource creation with business logic - [ ] `Updater` - Handle updates with validation - [ ] `Destroyer` - Handle deletion with cleanup - [ ] `Publisher` - State transition logic - [ ] `Archiver` - Archival workflow - [ ] **Query Services:** - [ ] `Finder` - Complex finding logic - [ ] `Searcher` - Search implementation - [ ] `Filter` - Advanced filtering - [ ] **Integration Services:** - [ ] `ExternalApiClient` - Third-party API interactions - [ ] `Importer` - Data import logic - [ ] `Exporter` - Data export logic ### Business Rules Documentation ``` Rule 1: [Business rule description] - Validation: [How to validate] - Error handling: [What to do on failure] - Edge cases: [Specific scenarios] Rule 2: [Business rule description] - Implementation: [Service/Model/Validator] - Dependencies: [Other systems/rules] ``` --- ## 6. Background Jobs & Async Processing ### Job Structure ```ruby # app/jobs/feature_name/processing_job.rb module FeatureName class ProcessingJob < ApplicationJob queue_as :default # Retry configuration retry_on StandardError, wait: :exponentially_longer, attempts: 5 discard_on CustomError # Timeout protection sidekiq_options retry: 3, backtrace: true, timeout: 300 def perform(resource_id) resource = Resource.find(resource_id) # Idempotency check return if already_processed?(resource) # Main processing logic process_resource(resource) # Mark as processed mark_as_processed(resource) rescue ActiveRecord::RecordNotFound => e Rails.logger.warn("Resource #{resource_id} not found: #{e.message}") # Don't retry for deleted records rescue CustomError => e handle_custom_error(resource, e) end private def already_processed?(resource) resource.processed_at.present? end def process_resource(resource) # Processing logic end def mark_as_processed(resource) resource.update!(processed_at: Time.current) end def handle_custom_error(resource, error) ErrorTracker.notify(error, resource_id: resource.id) resource.mark_as_failed! end end end ``` ### Job Queue Strategy ``` Queue Priorities (Sidekiq): - critical: User-facing operations (< 1s) - high: Time-sensitive background tasks (< 30s) - default: Standard background processing (< 5m) - low: Bulk operations, cleanup tasks (< 30m) - mailers: Email delivery - scheduled: Cron-like periodic jobs ``` ### Async Operations - [ ] **Immediate Processing:** - [ ] [Operation] - Response time requirement - [ ] **Background Processing:** - [ ] [Operation] - Queue: [critical/high/default/low] - [ ] Estimated processing time: [X seconds/minutes] - [ ] Concurrency requirements: [X workers] - [ ] **Scheduled Jobs:** - [ ] [Operation] - Schedule: [cron expression] - [ ] Expected duration: [X minutes] - [ ] Resource requirements ### Job Monitoring - [ ] Sidekiq Pro features needed: [Batches/Rate limiting/Reliability] - [ ] Dead job handling strategy - [ ] Job failure alerting threshold: [X% failure rate] - [ ] Processing time alerting: [If > X minutes] --- ## 7. Performance Considerations ### Query Optimization Strategy #### N+1 Query Prevention ```ruby # Bad - N+1 queries @users.each do |user| user.posts.each do |post| puts post.comments.count end end # Good - Eager loading @users.includes(posts: :comments).each do |user| user.posts.each do |post| puts post.comments.size end end ``` #### Optimized Query Patterns ```ruby # Use select to limit columns User.select(:id, :name, :email).where(active: true) # Use pluck for single values User.where(active: true).pluck(:id) # Use exists? instead of count for boolean checks User.where(role: 'admin').exists? # Use find_each for large datasets User.find_each(batch_size: 1000) do |user| # Process user end # Use counter_cache for counts has_many :posts, counter_cache: true ``` #### Database Query Limits - [ ] Index usage verified with EXPLAIN ANALYZE - [ ] Query time target: < 100ms for 95th percentile - [ ] Maximum records per query: [1000 without pagination] - [ ] Join limit: [Maximum 3 joins per query] ### Caching Strategy #### Cache Layers ```ruby # 1. Fragment Caching (View layer) <% cache @user do %> <%= render @user %> <% end %> # 2. Russian Doll Caching <% cache @user do %> <%= @user.name %> <% cache @user.posts do %> <%= render @user.posts %> <% end %> <% end %> # 3. Low-level Caching (Application layer) Rails.cache.fetch("user_stats_#{user.id}", expires_in: 1.hour) do expensive_calculation(user) end # 4. HTTP Caching (CDN layer) expires_in 1.hour, public: true fresh_when @resource, public: true ``` #### Cache Configuration - [ ] **Cache Store:** [Redis/Memcached] - [ ] **Cache Keys:** ```ruby # Pattern: [namespace]:[model]:[id]:[version]:[variant] cache_key = "v1:user:#{user.id}:dashboard:#{I18n.locale}" ``` - [ ] **TTL Strategy:** - Hot data (frequently accessed): 5-15 minutes - Warm data (regular access): 1-6 hours - Cold data (infrequent access): 24 hours - [ ] **Cache Invalidation:** ```ruby # Model callbacks after_save :clear_cache after_destroy :clear_cache def clear_cache Rails.cache.delete("user_stats_#{id}") Rails.cache.delete_matched("user_#{id}:*") end ``` #### Cache Warming Strategy ```ruby # Pre-populate cache for predictable access patterns class CacheWarmingJob < ApplicationJob def perform User.find_each do |user| Rails.cache.fetch("user_dashboard_#{user.id}", expires_in: 1.hour) do DashboardService.new(user).generate_data end end end end ``` ### Scalability Concerns #### Horizontal Scaling Readiness - [ ] **Stateless Application:** - [ ] No file system dependencies (use S3/cloud storage) - [ ] Session storage in Redis/database - [ ] No local caching (use distributed cache) - [ ] **Database Scaling:** - [ ] Read replicas for read-heavy operations - [ ] Connection pooling configured: [Size: X] - [ ] Database sharding strategy (if needed) - [ ] **Load Balancing:** - [ ] Health check endpoint: `/health` - [ ] Graceful shutdown handling - [ ] Request timeout configuration: [30 seconds] #### Concurrency Handling ```ruby # Pessimistic locking for critical sections User.transaction do user = User.lock.find(user_id) user.balance -= amount user.save! end # Optimistic locking with version column # Add lock_version column to table user.update!(name: 'New Name') # Raises ActiveRecord::StaleObjectError if version changed ``` #### Performance Targets ``` Current Load: - Concurrent users: [X] - Requests per second: [X RPS] - Average response time: [X ms] Target Load (6 months): - Concurrent users: [X] - Requests per second: [X RPS] - Average response time: [< X ms] - 95th percentile: [< X ms] - 99th percentile: [< X ms] ``` #### Resource Monitoring - [ ] Application metrics: [AppSignal/New Relic/DataDog] - [ ] Database query monitoring: [PgHero/Scout] - [ ] Memory profiling: [memory_profiler gem] - [ ] Alerting thresholds configured --- ## 8. Security Considerations ### Authentication - [ ] **Devise Configuration:** ```ruby devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable, :confirmable, :lockable, :timeoutable, :trackable # Timeout configuration config.timeout_in = 30.minutes # Password requirements config.password_length = 12..128 config.password_complexity = { digit: 1, lower: 1, upper: 1, special: 1 } ``` - [ ] **JWT Token Authentication (API):** ```ruby # Token expiration - Access token: 15 minutes - Refresh token: 7 days # Token storage - Secure, httpOnly cookies (web) - Encrypted storage (mobile) # Token revocation - Blacklist in Redis with TTL ``` ### Authorization - [ ] **Pundit Policies:** ```ruby # app/policies/resource_policy.rb class ResourcePolicy < ApplicationPolicy def index? user.has_role?(:admin) || record.public? end def show? user.has_role?(:admin) || record.user_id == user.id || record.public? end def create? user.has_role?(:admin, :editor) end def update? user.has_role?(:admin) || record.user_id == user.id end def destroy? user.has_role?(:admin) || (record.user_id == user.id && record.deletable?) end class Scope < Scope def resolve if user.has_role?(:admin) scope.all else scope.where(user_id: user.id).or(scope.where(public: true)) end end end end ``` - [ ] **Rolify Configuration:** ```ruby # Role hierarchy - super_admin: Full system access - admin: Organization-level access - manager: Team-level access - editor: Content creation/editing - viewer: Read-only access # Role scoping user.add_role(:admin, organization) user.has_role?(:admin, organization) ``` ### Data Protection - [ ] **Sensitive Data Encryption:** ```ruby # Active Record Encryption (Rails 7+) encrypts :ssn, deterministic: true encrypts :credit_card, deterministic: false # attr_encrypted gem (Rails < 7) attr_encrypted :ssn, key: :encryption_key # Encrypted columns in DB - SSN, Tax ID - Credit card numbers - Bank account information - Personal health information ``` - [ ] **PII Handling:** - [ ] Data anonymization for logs - [ ] GDPR compliance: Right to deletion - [ ] Data retention policy: [X months/years] - [ ] Audit trail for data access - [ ] **SQL Injection Prevention:** ```ruby # Bad User.where("email = '#{params[:email]}'") # Good User.where(email: params[:email]) User.where("email = ?", params[:email]) User.where("email = :email", email: params[:email]) ``` ### Input Validation & Sanitization - [ ] **Strong Parameters:** ```ruby def resource_params params.require(:resource).permit( :name, :description, :status, metadata: [:key1, :key2], tags: [] ) end ``` - [ ] **XSS Prevention:** ```ruby # Automatic escaping in ERB <%= user.name %> # Escaped <%= raw user.name %> # Unescaped - use with caution <%= sanitize user.bio, tags: %w(p br strong em) %> # Whitelist tags ``` - [ ] **CSRF Protection:** ```ruby # Enabled by default protect_from_forgery with: :exception # API exception skip_before_action :verify_authenticity_token, if: :json_request? ``` ### API Security - [ ] **Rate Limiting (Rack::Attack):** ```ruby # config/initializers/rack_attack.rb Rack::Attack.throttle("api/ip", limit: 300, period: 5.minutes) do |req| req.ip if req.path.start_with?('/api/') end Rack::Attack.throttle("api/email", limit: 5, period: 20.seconds) do |req| req.params['email'] if req.path == '/api/v1/login' && req.post? end ``` - [ ] **CORS Configuration:** ```ruby # config/initializers/cors.rb Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins 'https://example.com', 'https://app.example.com' resource '/api/*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options], credentials: true, max_age: 86400 end end ``` ### Audit Logging - [ ] **PaperTrail for Model Changes:** ```ruby class Resource < ApplicationRecord has_paper_trail on: [:create, :update, :destroy], ignore: [:updated_at, :view_count], meta: { user_id: :current_user_id } end ``` - [ ] **Security Events to Log:** - Authentication attempts (success/failure) - Authorization failures - Password changes - Role/permission changes - Sensitive data access - API key usage --- ## 9. Third-Party Integrations ### External Services - [ ] **Service Name:** [e.g., Stripe, SendGrid, AWS S3] - **Purpose:** [What does it do?] - **Authentication:** [API key, OAuth, etc.] - **Rate Limits:** [Requests per second/minute] - **Error Handling:** [Retry strategy, fallback] - **Monitoring:** [How to track usage/errors] ### Integration Pattern ```ruby # app/services/external_api/client.rb module ExternalApi class Client include HTTParty base_uri ENV['EXTERNAL_API_URL'] def initialize @auth_token = ENV['EXTERNAL_API_TOKEN'] @options = { headers: { 'Authorization' => "Bearer #{@auth_token}", 'Content-Type' => 'application/json' }, timeout: 10, # seconds open_timeout: 5 } end def create_resource(params) response = self.class.post('/resources', @options.merge(body: params.to_json)) handle_response(response) rescue HTTParty::Error, Timeout::Error => e handle_error(e) end private def handle_response(response) case response.code when 200..299 Result.success(response.parsed_response) when 400..499 Result.failure(:client_error, response.parsed_response) when 500..599 Result.failure(:server_error, response.parsed_response) else Result.failure(:unknown_error, response) end end def handle_error(error) ErrorTracker.notify(error) Result.failure(:network_error, error.message) end end end ``` ### Webhook Handling ```ruby # app/controllers/webhooks/external_service_controller.rb module Webhooks class ExternalServiceController < ApplicationController skip_before_action :verify_authenticity_token before_action :verify_webhook_signature def create # Idempotency check return head :ok if webhook_already_processed? # Queue for async processing ExternalServiceWebhookJob.perform_later(webhook_params.to_json) head :ok rescue StandardError => e ErrorTracker.notify(e) head :unprocessable_entity end private def verify_webhook_signature signature = request.headers['X-Webhook-Signature'] expected = OpenSSL::HMAC.hexdigest('SHA256', webhook_secret, request.raw_post) head :unauthorized unless ActiveSupport::SecurityUtils.secure_compare(signature, expected) end def webhook_already_processed? WebhookEvent.exists?(external_id: webhook_params[:id]) end def webhook_secret ENV['EXTERNAL_SERVICE_WEBHOOK_SECRET'] end end end ``` ### Dependency Management - [ ] **Gem Dependencies:** ```ruby # Gemfile gem 'httparty', '~> 0.21' # HTTP client gem 'aws-sdk-s3', '~> 1.0' # AWS S3 gem 'stripe', '~> 8.0' # Payment processing gem 'twilio-ruby', '~> 5.0' # SMS notifications ``` - [ ] **Version Pinning Strategy:** - Major versions: Pinned (breaking changes) - Minor versions: Optimistic (features) - Patch versions: Open (bug fixes) - [ ] **Dependency Audit:** - [ ] `bundle audit` for security vulnerabilities - [ ] Dependabot/Renovate for automated updates - [ ] Regular review cycle: [Monthly] --- ## 10. Error Handling & Logging ### Error Handling Strategy #### Controller Error Handling ```ruby # app/controllers/application_controller.rb class ApplicationController < ActionController::Base rescue_from ActiveRecord::RecordNotFound, with: :not_found rescue_from ActiveRecord::RecordInvalid, with: :unprocessable_entity rescue_from Pundit::NotAuthorizedError, with: :forbidden rescue_from ActionController::ParameterMissing, with: :bad_request private def not_found(exception) respond_to do |format| format.html { render 'errors/404', status: :not_found } format.json { render json: { error: 'Resource not found' }, status: :not_found } end end def unprocessable_entity(exception) respond_to do |format| format.html { redirect_back fallback_location: root_path, alert: exception.message } format.json { render json: { errors: exception.record.errors }, status: :unprocessable_entity } end end def forbidden(exception) respond_to do |format| format.html { render 'errors/403', status: :forbidden } format.json { render json: { error: 'Access forbidden' }, status: :forbidden } end end def bad_request(exception) respond_to do |format| format.html { redirect_back fallback_location: root_path, alert: 'Bad request' } format.json { render json: { error: exception.message }, status: :bad_request } end end end ``` #### Service Error Handling ```ruby # app/services/concerns/error_handling.rb module ErrorHandling extend ActiveSupport::Concern class ServiceError < StandardError; end class ValidationError < ServiceError; end class BusinessRuleError < ServiceError; end class ExternalServiceError < ServiceError; end included do def handle_errors yield rescue ActiveRecord::RecordInvalid => e log_error(e) Failure([:validation_error, e.record.errors]) rescue BusinessRuleError => e log_error(e) Failure([:business_rule_violation, e.message]) rescue ExternalServiceError => e log_error(e) ErrorTracker.notify(e) Failure([:external_service_error, e.message]) rescue StandardError => e log_error(e) ErrorTracker.notify(e) Failure([:unexpected_error, 'An unexpected error occurred']) end end private def log_error(error) Rails.logger.error("#{self.class.name} Error: #{error.message}") Rails.logger.error(error.backtrace.join("\n")) end end ``` ### Logging Strategy #### Structured Logging ```ruby # config/initializers/lograge.rb Rails.application.configure do config.lograge.enabled = true config.lograge.formatter = Lograge::Formatters::Json.new config.lograge.custom_options = lambda do |event| { time: event.time, user_id: event.payload[:user_id], request_id: event.payload[:request_id], ip: event.payload[:ip], params: event.payload[:params].except('controller', 'action') } end end ``` #### Application Logging ```ruby # Use structured logging Rails.logger.info({ event: 'resource_created', user_id: user.id, resource_id: resource.id, resource_type: resource.class.name, metadata: { source: 'api', version: 'v1' } }.to_json) # Log levels # DEBUG: Detailed information for diagnosing problems # INFO: Confirmation that things are working as expected # WARN: Something unexpected happened, but application continues # ERROR: Serious problem, feature may not work correctly # FATAL: Critical problem, application may crash ``` #### Performance Logging ```ruby # Log slow queries (config/application.rb) config.active_record.query_log_tags_enabled = true config.active_record.slow_query_threshold = 0.5 # 500ms # Custom performance logging ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args| event = ActiveSupport::Notifications::Event.new(*args) if event.duration > 1000 Rails.logger.warn({ event: 'slow_request', controller: event.payload[:controller], action: event.payload[:action], duration_ms: event.duration, db_runtime_ms: event.payload[:db_runtime] }.to_json) end end ``` #### Log Retention - [ ] **Development:** 7 days - [ ] **Staging:** 30 days - [ ] **Production:** 90 days (compliance requirement) - [ ] **Audit logs:** [Depends on compliance - potentially indefinite] ### Error Tracking - [ ] **Tool:** [Sentry/Rollbar/Honeybadger/Airbrake] - [ ] **Configuration:** ```ruby # config/initializers/sentry.rb Sentry.init do |config| config.dsn = ENV['SENTRY_DSN'] config.breadcrumbs_logger = [:active_support_logger, :http_logger] config.traces_sample_rate = 0.1 # 10% of requests config.profiles_sample_rate = 0.1 config.before_send = lambda do |event, hint| # Filter sensitive data event.request.data = filter_sensitive_data(event.request.data) event end end ``` - [ ] **Alert Configuration:** - Critical errors: Immediate Slack notification - Error rate spike: > 1% increase - New error types: First occurrence - Resolved errors: Re-occurrence notification --- ## 11. Code Organization & Structure ### Directory Structure ``` app/ ├── controllers/ │ └── [feature_name]/ │ ├── base_controller.rb │ └── resources_controller.rb ├── models/ │ └── [feature_name]/ │ ├── resource.rb │ └── concerns/ │ └── searchable.rb ├── services/ │ └── [feature_name]/ │ ├── creator.rb │ ├── updater.rb │ └── destroyer.rb ├── queries/ │ └── [feature_name]/ │ └── resource_query.rb ├── forms/ │ └── [feature_name]/ │ └── resource_form.rb ├── decorators/ │ └── [feature_name]/ │ └── resource_decorator.rb ├── serializers/ │ └── [feature_name]/ │ └── resource_serializer.rb ├── policies/ │ └── [feature_name]/ │ └── resource_policy.rb ├── jobs/ │ └── [feature_name]/ │ └── processing_job.rb ├── mailers/ │ └── [feature_name]/ │ └── notification_mailer.rb └── views/ └── [feature_name]/ └── resources/ ``` ### Concerns Pattern ```ruby # app/models/concerns/searchable.rb module Searchable extend ActiveSupport::Concern included do include PgSearch::Model pg_search_scope :search_by_all, against: [:name, :description], using: { tsearch: { prefix: true, dictionary: 'english' } } end class_methods do def search(query) return all if query.blank? search_by_all(query) end end end ``` ### Decorator Pattern (Draper) ```ruby # app/decorators/resource_decorator.rb class ResourceDecorator < Draper::Decorator delegate_all def formatted_created_at object.created_at.strftime('%B %d, %Y') end def status_badge h.content_tag :span, object.status.titleize, class: "badge badge-#{status_color}" end def description_preview(length = 100) h.truncate(object.description, length: length, separator: ' ') end private def status_color case object.status when 'published' then 'success' when 'draft' then 'secondary' when 'archived' then 'danger' else 'primary' end end end ``` ### Form Objects ```ruby # app/forms/feature_name/resource_form.rb module FeatureName class ResourceForm include ActiveModel::Model include ActiveModel::Attributes attribute :name, :string attribute :description, :text attribute :status, :integer attribute :tags, array: true, default: [] validates :name, presence: true, length: { maximum: 255 } validates :description, presence: true validates :status, inclusion: { in: Resource.statuses.keys } def save return false unless valid? persist! end def persist! Resource.create!( name: name, description: description, status: status, tags: tags ) end end end ``` ### Query Objects ```ruby # app/queries/feature_name/resource_query.rb module FeatureName class ResourceQuery attr_reader :relation def initialize(relation = Resource.all) @relation = relation end def call(filters = {}) @relation = apply_status_filter(filters[:status]) @relation = apply_date_filter(filters[:created_after], filters[:created_before]) @relation = apply_search(filters[:search]) @relation = apply_sorting(filters[:sort]) @relation end private def apply_status_filter(status) return @relation if status.blank? @relation.where(status: status) end def apply_date_filter(created_after, created_before) @relation = @relation.where('created_at >= ?', created_after) if created_after.present? @relation = @relation.where('created_at <= ?', created_before) if created_before.present? @relation end def apply_search(search) return @relation if search.blank? @relation.search(search) end def apply_sorting(sort) return @relation.order(created_at: :desc) if sort.blank? direction = sort.start_with?('-') ? :desc : :asc column = sort.delete_prefix('-') @relation.order(column => direction) end end end ``` ### ViewComponent Pattern ```ruby # app/components/feature_name/resource_card_component.rb module FeatureName class ResourceCardComponent < ViewComponent::Base attr_reader :resource def initialize(resource:, show_actions: true) @resource = resource @show_actions = show_actions end def status_color case resource.status when 'published' then 'green' when 'draft' then 'gray' when 'archived' then 'red' end end def show_actions? @show_actions && policy(@resource).update? end private def policy(record) Pundit.policy!(helpers.current_user, record) end end end ``` --- ## 12. Testing Strategy ### Test Coverage Requirements ``` Minimum Coverage Targets: - Models: 95% - Services: 90% - Controllers: 85% - Jobs: 90% - Overall: 85% ``` ### Unit Tests (Models) ```ruby # spec/models/resource_spec.rb require 'rails_helper' RSpec.describe Resource, type: :model do describe 'associations' do it { should belong_to(:user) } it { should have_many(:comments).dependent(:destroy) } end describe 'validations' do subject { build(:resource) } it { should validate_presence_of(:name) } it { should validate_uniqueness_of(:name).scoped_to(:user_id) } it { should validate_length_of(:name).is_at_most(255) } end describe 'scopes' do describe '.active' do it 'returns only active resources' do active = create(:resource, status: 'published') inactive = create(:resource, status: 'archived') expect(Resource.active).to include(active) expect(Resource.active).not_to include(inactive) end end end describe 'callbacks' do it 'schedules processing job after creation' do expect { create(:resource) }.to have_enqueued_job(ProcessingJob) end end describe '#publishable?' do context 'when resource meets requirements' do it 'returns true' do resource = build(:resource, :with_required_fields) expect(resource.publishable?).to be true end end context 'when resource is missing required fields' do it 'returns false' do resource = build(:resource, description: nil) expect(resource.publishable?).to be false end end end end ``` ### Service Object Tests ```ruby # spec/services/feature_name/creator_spec.rb require 'rails_helper' RSpec.describe FeatureName::Creator do let(:user) { create(:user) } let(:valid_params) { attributes_for(:resource) } subject(:service) { described_class.new(user: user, params: valid_params) } describe '#call' do context 'with valid parameters' do it 'creates a resource' do expect { service.call }.to change(Resource, :count).by(1) end it 'returns success result' do result = service.call expect(result).to be_success expect(result.value!).to be_a(Resource) end it 'enqueues notification job' do expect { service.call }.to have_enqueued_job(NotificationJob) end end context 'with invalid parameters' do let(:valid_params) { { name: '' } } it 'does not create a resource' do expect { service.call }.not_to change(Resource, :count) end it 'returns failure result' do result = service.call expect(result).to be_failure end end context 'when business rules are violated' do before do allow(user).to receive(:resource_limit).and_return(0) end it 'returns failure with business rule violation' do result = service.call expect(result).to be_failure expect(result.failure.first).to eq(:business_rule_violation) end end context 'when external service fails' do before do allow(ExternalService).to receive(:call).and_raise(StandardError) end it 'handles the error gracefully' do result = service.call expect(result).to be_failure end it 'notifies error tracker' do expect(ErrorTracker).to receive(:notify) service.call end end end end ``` ### Controller Tests (Request Specs) ```ruby # spec/requests/resources_spec.rb require 'rails_helper' RSpec.describe 'Resources', type: :request do let(:user) { create(:user) } let(:resource) { create(:resource, user: user) } before { sign_in user } describe 'GET /resources' do it 'returns success response' do get resources_path expect(response).to have_http_status(:success) end it 'returns resources in JSON format' do create_list(:resource, 3, user: user) get resources_path, params: { format: :json } expect(response).to have_http_status(:success) json = JSON.parse(response.body) expect(json['data'].length).to eq(3) end it 'filters resources by status' do published = create(:resource, user: user, status: 'published') draft = create(:resource, user: user, status: 'draft') get resources_path, params: { filter: { status: 'published' } } expect(response.body).to include(published.name) expect(response.body).not_to include(draft.name) end it 'paginates results' do create_list(:resource, 30, user: user) get resources_path, params: { page: 2, per_page: 10 } json = JSON.parse(response.body) expect(json['meta']['page']).to eq(2) expect(json['data'].length).to eq(10) end end describe 'POST /resources' do context 'with valid parameters' do let(:valid_params) { { resource: attributes_for(:resource) } } it 'creates a new resource' do expect { post resources_path, params: valid_params }.to change(Resource, :count).by(1) end it 'returns created status' do post resources_path, params: valid_params expect(response).to have_http_status(:created) end end context 'with invalid parameters' do let(:invalid_params) { { resource: { name: '' } } } it 'does not create a resource' do expect { post resources_path, params: invalid_params }.not_to change(Resource, :count) end it 'returns unprocessable entity status' do post resources_path, params: invalid_params expect(response).to have_http_status(:unprocessable_entity) end it 'returns error messages' do post resources_path, params: invalid_params json = JSON.parse(response.body) expect(json['errors']).to be_present end end end describe 'authorization' do let(:other_user) { create(:user) } let(:resource) { create(:resource, user: other_user) } it 'prevents unauthorized access' do get resource_path(resource) expect(response).to have_http_status(:forbidden) end end end ``` ### Integration Tests (System Specs) ```ruby # spec/system/resources_spec.rb require 'rails_helper' RSpec.describe 'Resource Management', type: :system do let(:user) { create(:user) } before do driven_by(:selenium_chrome_headless) login_as(user, scope: :user) end describe 'Creating a resource' do it 'allows user to create a new resource', js: true do visit resources_path click_link 'New Resource' fill_in 'Name', with: 'Test Resource' fill_in 'Description', with: 'Test Description' select 'Published', from: 'Status' expect { click_button 'Create Resource' }.to change(Resource, :count).by(1) expect(page).to have_content('Resource was successfully created') expect(page).to have_content('Test Resource') end it 'displays validation errors', js: true do visit new_resource_path click_button 'Create Resource' expect(page).to have_content("Name can't be blank") end end describe 'Searching resources' do before do create(:resource, name: 'Alpha Resource', user: user) create(:resource, name: 'Beta Resource', user: user) end it 'filters resources by search term', js: true do visit resources_path fill_in 'Search', with: 'Alpha' click_button 'Search' expect(page).to have_content('Alpha Resource') expect(page).not_to have_content('Beta Resource') end end end ``` ### Job Tests ```ruby # spec/jobs/feature_name/processing_job_spec.rb require 'rails_helper' RSpec.describe FeatureName::ProcessingJob, type: :job do let(:resource) { create(:resource) } describe '#perform' do it 'processes the resource' do expect { described_class.perform_now(resource.id) }.to change { resource.reload.processed_at }.from(nil) end it 'is idempotent' do described_class.perform_now(resource.id) first_timestamp = resource.reload.processed_at described_class.perform_now(resource.id) second_timestamp = resource.reload.processed_at expect(first_timestamp).to eq(second_timestamp) end context 'when resource is not found' do it 'logs warning and does not retry' do expect(Rails.logger).to receive(:warn) expect { described_class.perform_now(999999) }.not_to raise_error end end context 'when processing fails' do before do allow_any_instance_of(Resource).to receive(:process!).and_raise(StandardError) end it 'notifies error tracker' do expect(ErrorTracker).to receive(:notify) expect { described_class.perform_now(resource.id) }.to raise_error(StandardError) end end end end ``` ### Performance Tests ```ruby # spec/performance/resources_spec.rb require 'rails_helper' RSpec.describe 'Resource Performance', type: :performance do describe 'listing resources' do it 'loads efficiently with many records' do user = create(:user) create_list(:resource, 1000, user: user) expect { ResourceQuery.new(user.resources).call }.to perform_under(100).ms end it 'avoids N+1 queries' do user = create(:user) create_list(:resource, 10, user: user) expect { resources = ResourceQuery.new(user.resources).call resources.each do |resource| resource.user.name resource.comments.count end }.to perform_queries(3) # 1 for resources, 1 for users, 1 for comments end end describe 'searching resources' do it 'performs search efficiently' do user = create(:user) create_list(:resource, 1000, user: user) expect { Resource.search('test query') }.to perform_under(50).ms end end end ``` ### Test Data (Factories) ```ruby # spec/factories/resources.rb FactoryBot.define do factory :resource do association :user sequence(:name) { |n| "Resource #{n}" } description { Faker::Lorem.paragraph } status { :draft } metadata { {} } trait :published do status { :published } published_at { Time.current } end trait :archived do status { :archived } end trait :with_comments do after(:create) do |resource| create_list(:comment, 3, resource: resource) end end trait :with_required_fields do description { Faker::Lorem.paragraph(sentence_count: 5) } metadata { { required_field: 'value' } } end end end ``` ### Test Coverage Enforcement ```ruby # spec/spec_helper.rb require 'simplecov' SimpleCov.start 'rails' do add_filter '/spec/' add_filter '/config/' add_filter '/vendor/' add_group 'Services', 'app/services' add_group 'Queries', 'app/queries' add_group 'Jobs', 'app/jobs' add_group 'Policies', 'app/policies' minimum_coverage 85 minimum_coverage_by_file 80 end ``` --- ## 13. Deployment & Rollback Considerations ### Pre-Deployment Checklist - [ ] All tests passing in CI/CD - [ ] Code review approved by [X] senior developers - [ ] Database migrations reviewed for: - [ ] Reversibility - [ ] Performance impact on large tables - [ ] Downtime requirements - [ ] Index creation strategy (concurrent) - [ ] Feature flags configured (if applicable) - [ ] Environment variables set in staging/production - [ ] Third-party service credentials verified - [ ] Monitoring alerts configured - [ ] Documentation updated - [ ] Security scan passed (Brakeman, bundle audit) ### Migration Strategy #### Zero-Downtime Migration Pattern ```ruby # Step 1: Add new column (nullable) class AddNewColumnToResources < ActiveRecord::Migration[7.1] def change add_column :resources, :new_field, :string # Don't add NOT NULL constraint yet end end # Step 2: Backfill existing data (in separate deployment) class BackfillNewFieldInResources < ActiveRecord::Migration[7.1] disable_ddl_transaction! def up Resource.in_batches(of: 1000) do |batch| batch.update_all("new_field = old_field") sleep(0.1) # Throttle to reduce load end end def down # No-op, safe to leave data end end # Step 3: Add NOT NULL constraint (in separate deployment) class AddNotNullToNewField < ActiveRecord::Migration[7.1] def change change_column_null :resources, :new_field, false end end # Step 4: Remove old column (in separate deployment, after verification) class RemoveOldFieldFromResources < ActiveRecord::Migration[7.1] def change remove_column :resources, :old_field, :string end end ``` #### Concurrent Index Creation ```ruby class AddIndexToResourcesEmail < ActiveRecord::Migration[7.1] disable_ddl_transaction! def change add_index :resources, :email, algorithm: :concurrently end end ``` ### Deployment Steps ```bash # 1. Deploy code to staging cap staging deploy # 2. Run smoke tests on staging bundle exec rspec spec/smoke # 3. Deploy to production (blue-green deployment) cap production deploy # 4. Run database migrations cap production deploy:migrate # 5. Restart application servers (rolling restart) cap production deploy:restart # 6. Verify deployment cap production deploy:verify # 7. Enable new feature (via feature flag) FeatureFlag.enable(:new_feature) ``` ### Feature Flags ```ruby # Use Flipper for feature flags class ResourcesController < ApplicationController def create if Flipper.enabled?(:new_resource_creation_flow, current_user) # New implementation result = NewFeatureName::Creator.new(user: current_user, params: resource_params).call else # Old implementation @resource = current_user.resources.create(resource_params) end end end # Gradual rollout strategy Flipper.enable_percentage_of_time(:new_feature, 10) # 10% of requests Flipper.enable_percentage_of_actors(:new_feature, 25) # 25% of users Flipper.enable_actor(:new_feature, user) # Specific user Flipper.enable_group(:new_feature, :admins) # Admin group Flipper.enable(:new_feature) # All users ``` ### Rollback Strategy #### Immediate Rollback Triggers - Error rate > 5% increase - Response time > 2x baseline - Critical feature broken - Security vulnerability discovered #### Rollback Procedure ```bash # 1. Disable feature flag (immediate) Flipper.disable(:new_feature) # 2. Revert code deployment (if needed) cap production deploy:rollback # 3. Rollback database migrations (if safe) cap production deploy:migrate:rollback # 4. Verify rollback cap production deploy:verify # 5. Post-incident review # - Document what went wrong # - Identify root cause # - Create action items ``` #### Migration Rollback Considerations ```ruby # Make migrations reversible class AddColumnsToResources < ActiveRecord::Migration[7.1] def change add_column :resources, :new_field, :string # Rails can automatically reverse this end end # Or explicitly define up/down class ComplexMigration < ActiveRecord::Migration[7.1] def up # Forward migration end def down # Explicit rollback steps end end # Mark irreversible migrations class IrreversibleMigration < ActiveRecord::Migration[7.1] def up execute "ALTER TABLE resources ..." end def down raise ActiveRecord::IrreversibleMigration end end ``` ### Post-Deployment Monitoring #### Health Checks ```ruby # config/routes.rb get '/health', to: 'health#index' # app/controllers/health_controller.rb class HealthController < ApplicationController skip_before_action :authenticate_user! def index checks = { database: database_connected?, redis: redis_connected?, sidekiq: sidekiq_running?, external_services: external_services_healthy? } status = checks.values.all? ? :ok : :service_unavailable render json: { status: status, checks: checks, timestamp: Time.current }, status: status end private def database_connected? ActiveRecord::Base.connection.active? rescue false end def redis_connected? Redis.current.ping == 'PONG' rescue false end def sidekiq_running? Sidekiq::ProcessSet.new.size > 0 rescue false end def external_services_healthy? # Check critical external services true end end ``` #### Monitoring Checklist (First 24 hours) - [ ] Error rate within acceptable range (< 1%) - [ ] Response time within SLA (95th percentile < Xms) - [ ] Database query performance stable - [ ] Background job processing rate normal - [ ] Memory usage stable (no memory leaks) - [ ] External API calls succeeding - [ ] User-reported issues tracked - [ ] Business metrics tracking correctly ### Documentation Requirements - [ ] **Technical Specs Updated:** - [ ] Architecture diagrams - [ ] API documentation - [ ] Database schema documentation - [ ] **Runbooks Updated:** - [ ] Deployment procedure - [ ] Rollback procedure - [ ] Troubleshooting guide - [ ] Monitoring dashboard links - [ ] **User Documentation:** - [ ] Feature documentation - [ ] Release notes - [ ] Known limitations --- ## 14. Implementation Timeline ### Phase 1: Design & Planning (Week 1) - [ ] Finalize technical specifications - [ ] Database schema design review - [ ] API contract definition - [ ] Security review - [ ] Performance benchmarking plan ### Phase 2: Core Implementation (Week 2-3) - [ ] Database migrations - [ ] Models and associations - [ ] Service objects - [ ] Background jobs - [ ] Unit tests ### Phase 3: API & Integration (Week 4) - [ ] API endpoints - [ ] Third-party integrations - [ ] Authorization policies - [ ] Integration tests ### Phase 4: UI & UX (Week 5) - [ ] Controllers - [ ] Views/Components - [ ] Frontend logic (Stimulus) - [ ] System tests ### Phase 5: Testing & Optimization (Week 6) - [ ] Performance testing - [ ] Security testing - [ ] Load testing - [ ] Bug fixes ### Phase 6: Deployment (Week 7) - [ ] Staging deployment - [ ] User acceptance testing - [ ] Production deployment - [ ] Monitoring & support --- ## 15. Success Metrics ### Technical Metrics - [ ] Test coverage: [Target: 85%] - [ ] API response time: [Target: < 200ms p95] - [ ] Database query time: [Target: < 100ms p95] - [ ] Error rate: [Target: < 1%] - [ ] Background job processing time: [Target: < 5 minutes] ### Business Metrics - [ ] [Business metric 1]: [Target value] - [ ] [Business metric 2]: [Target value] - [ ] User adoption rate: [Target: X% within Y weeks] ### Performance Benchmarks ``` Load Testing Results (Expected): - Concurrent users: 1000 - Requests per second: 500 - Average response time: 150ms - 95th percentile: 300ms - 99th percentile: 500ms - Error rate: < 0.5% ``` --- ## 16. Risk Assessment ### Technical Risks | Risk | Probability | Impact | Mitigation Strategy | |------|-------------|--------|---------------------| | [Risk description] | High/Med/Low | High/Med/Low | [Mitigation approach] | | Database migration issues | Medium | High | Zero-downtime migration pattern, thorough testing in staging | | Third-party API downtime | Medium | Medium | Circuit breaker pattern, fallback mechanism, retry logic | | Performance degradation | Low | High | Load testing, monitoring, feature flags for gradual rollout | | Security vulnerabilities | Low | High | Security review, penetration testing, automated scanning | ### Dependencies & Blockers - [ ] **Dependency 1:** [Description] - Owner: [Name] - Due date: [Date] - Status: [Blocked/In Progress/Complete] --- ## 17. Open Questions & Decisions Needed - [ ] **Question 1:** [Question requiring stakeholder input] - Options: [A, B, C] - Recommendation: [Option X because Y] - Decision maker: [Name] - Deadline: [Date] --- ## 18. References & Resources ### Internal Documentation - [Link to existing codebase documentation] - [Link to architecture decision records] - [Link to API documentation] ### External Resources - [Relevant gem documentation] - [Rails guides reference] - [Design pattern resources] --- ## 19. Sign-off ### Technical Review - [ ] Backend Lead: [Name] - [Date] - [ ] Frontend Lead: [Name] - [Date] - [ ] DevOps Lead: [Name] - [Date] - [ ] Security Lead: [Name] - [Date] ### Approval - [ ] Engineering Manager: [Name] - [Date] - [ ] Product Manager: [Name] - [Date] --- **Last Updated:** [Date] **Document Owner:** [Name] **Status:** [Draft/In Review/Approved/In Progress/Complete]