Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions docs/architecture-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,59 @@ Uses `edx-rbac` for fine-grained permissions with:
### 15. Use ddt to parameterize unit tests
- Improve test DRYness by using the `ddt` packages `@data` and `@unpack` decorators.

### 16. Module Splitting with Re-exports
When files grow too large (>500-1000 lines), split them while maintaining backwards compatibility:

**Pattern:**
1. Create new submodule(s) with extracted code
2. Re-export from original module so existing imports continue to work
3. Add pylint/noqa comments to suppress false-positive warnings on re-exports

**Example - handlers.py (avoiding circular imports):**
```python
# bffs/base.py - base class with no handler dependencies
class BaseHandler:
...

# bffs/learner_portal/handlers.py - imports from base.py (not handlers.py)
from enterprise_access.apps.bffs.base import BaseHandler

class BaseLearnerPortalHandler(BaseHandler):
...

# bffs/handlers.py - pure re-exports for backwards compatibility
from enterprise_access.apps.bffs.base import BaseHandler
from enterprise_access.apps.bffs.learner_portal.handlers import (
BaseLearnerPortalHandler,
DashboardHandler,
)
```

**Example - models.py:**
```python
# models.py - keeps core models, re-exports supporting models
class SubsidyAccessPolicy:
...

# Re-export for backwards compatibility
# pylint: disable=wrong-import-position,unused-import
from .models_supporting import ( # noqa: E402,F401
PolicyGroupAssociation,
ForcedPolicyRedemption,
)
```

**When to use:**
- Files exceed ~1000 lines
- Clear domain boundaries exist for extraction
- ForeignKey relationships use string references (`'app.Model'`) to avoid circular imports
- Base classes go in separate `base.py` to prevent circular imports when child classes are in submodules

**Benefits:**
- No changes required to existing import statements
- Reduced cognitive load per file
- Easier testing and maintenance

### Key Takeaways for Implementation:
- Check permissions early using `@permission_required` decorator
- Use separate serializers for request/response
Expand Down
51 changes: 51 additions & 0 deletions enterprise_access/apps/bffs/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""
Base handler for bffs app.
"""
import logging

from enterprise_access.apps.bffs.context import BaseHandlerContext

logger = logging.getLogger(__name__)


class BaseHandler:
"""
A base handler class that provides shared core functionality for different BFF handlers.
The `BaseHandler` includes core methods for loading data and adding errors to the context.
"""

def __init__(self, context: BaseHandlerContext):
"""
Initializes the BaseHandler with a HandlerContext.
Args:
context (HandlerContext): The context object containing request information and data.
"""
self.context = context

def load_and_process(self):
"""
Loads and processes data. This method should be extended by subclasses to implement
specific data loading and transformation logic.
"""
raise NotImplementedError("Subclasses must implement `load_and_process` method.")

def add_error(self, user_message, developer_message, status_code=None):
"""
Adds an error to the context.
Output fields determined by the ErrorSerializer
"""
self.context.add_error(
user_message=user_message,
developer_message=developer_message,
status_code=status_code,
)

def add_warning(self, user_message, developer_message):
"""
Adds an error to the context.
Comment thread
iloveagent57 marked this conversation as resolved.
Output fields determined by the WarningSerializer
"""
self.context.add_warning(
user_message=user_message,
developer_message=developer_message,
)
Loading
Loading