Development practices, workflows, and technical architecture guide for Equevu
This document defines the coding standards and conventions for the Equevu fintech platform. All code must adhere to these standards to ensure consistency, maintainability, and alignment with our repository pattern architecture.
# Standard library imports
import os
import sys
from datetime import datetime
# Third-party imports
import requests
from django.db import models
from rest_framework import serializers
# Local application imports
from apps.companies.repository.company_repository import CompanyRepository
from apps.companies.services.company_service import CompanyService
{Model}Repository (e.g., CompanyRepository){Domain}Service (e.g., CompanyService, PaymentService){Service}Client (e.g., S3Client, TwilioClient)test_{module}.pyTest{ClassName}test_{method_description}Optional for nullable valuesList, Dict, Tuple from typing modulefrom typing import Optional, List, Dict
from decimal import Decimal
def calculate_contribution(
employee_id: int,
salary: Decimal,
is_native: bool = False
) -> Dict[str, Decimal]:
pass
All code must follow the repository pattern architecture as defined in architecture.md. Key principles:
Refer to the architecture document for detailed implementation examples and migration strategies.
Each Django app must follow this structure:
app_name/
├── models.py # Minimal models only
├── repository/ # Data access layer
├── services/ # Business logic layer
├── external/ # External service integrations
├── utils/ # Helper functions
├── api_views.py # API endpoints
├── serializers.py # DRF serializers
├── urls.py
└── migrations/
get() without handling DoesNotExistselect_related() and prefetch_related() for optimization@transaction.atomic for multi-step operations, but be mindful of what’s inside:
# BAD - Slow operations inside transaction
@transaction.atomic
def process_payment(company_id, amount):
company = Company.objects.select_for_update().get(id=company_id)
# External API call inside transaction - BLOCKS OTHER OPERATIONS!
payment_result = external_payment_api.process(amount) # Could take 30+ seconds
if payment_result.success:
company.balance += amount
company.save()
# GOOD - Only critical operations inside transaction
def process_payment(company_id, amount):
# Prepare data outside transaction
company = Company.objects.get(id=company_id)
# External call outside transaction
payment_result = external_payment_api.process(amount)
if payment_result.success:
# Only the atomic database operations
with transaction.atomic():
Company.objects.filter(id=company_id).update(
balance=F('balance') + amount,
last_payment=timezone.now()
)
PaymentLog.objects.create(
company_id=company_id,
amount=amount,
reference=payment_result.reference
)
class CreateCompanySerializer(serializers.Serializer):
name = serializers.CharField(max_length=255)
email = serializers.EmailField()
def validate_email(self, value):
return value.lower() # Simple transformation only
class CompanyOutputSerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ['id', 'name', 'email', 'created_at']
{model}_repository.py{domain}_service.py{service}_client.pyvalidators.py, formatters.pyWithin classes, organize methods in this order:
__init__ and other special methods_)class CompanyService:
def __init__(
self,
company_repo: CompanyRepository = None,
notification_client: NotificationClient = None
):
self.company_repo = company_repo or CompanyRepository()
self.notification_client = notification_client or NotificationClient()
class TestCompanyService(TestCase):
def setUp(self):
self.service = CompanyService()
self.mock_repo = Mock(spec=CompanyRepository)
self.service.company_repo = self.mock_repo
def test_create_company_success(self):
# Arrange
data = {'name': 'Test Corp', 'email': 'test@example.com'}
self.mock_repo.get_by_email.return_value = None
# Act
result = self.service.create_company(data)
# Assert
self.assertEqual(result['status'], 'created')
self.mock_repo.create.assert_called_once()
Example Apidog test scenarios:
Every public class and method must have docstrings:
class CompanyService:
"""
Service layer for company-related business operations.
Handles company creation, updates, and business rule validation.
"""
def create_company(self, data: Dict) -> Dict:
"""
Create a new company with initial setup.
Args:
data: Dictionary containing company details
- name: Company name
- email: Company email
- phone_number: Contact number
Returns:
Dictionary with company_id and status
Raises:
ValueError: If company with email already exists
ValidationError: If data validation fails
"""
pass
def calculate_contribution(self, employee) -> Decimal:
"""Calculate employee contribution based on residency status."""
base_salary = employee.basic_salary or Decimal('0')
allowance = employee.allowance or Decimal('0')
total_salary = base_salary + allowance
# UAE nationals have higher contribution rate per government regulation
if employee.is_native_resident:
return total_salary * Decimal('0.125') # 12.5% for nationals
return total_salary * Decimal('0.05') # 5% for expats
# GOOD - Generic error message
except Exception as e:
logger.error(f"Company creation failed: {str(e)}")
return Response(
{'error': 'Failed to create company'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
# BAD - Exposing internal details
except Exception as e:
return Response(
{'error': str(e), 'stack_trace': traceback.format_exc()},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
Before submitting a PR, ensure:
Remember: These standards exist to ensure our codebase remains maintainable, secure, and scalable as our fintech platform grows.