feat(domain): add domain layer foundation -- enums, value objects, TechniqueEntity, repository ports
This commit is contained in:
0
backend/app/domain/ports/__init__.py
Normal file
0
backend/app/domain/ports/__init__.py
Normal file
4
backend/app/domain/ports/repositories/__init__.py
Normal file
4
backend/app/domain/ports/repositories/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from app.domain.ports.repositories.technique_repository import TechniqueRepository
|
||||
from app.domain.ports.repositories.test_repository import TestRepository
|
||||
|
||||
__all__ = ["TechniqueRepository", "TestRepository"]
|
||||
@@ -0,0 +1,57 @@
|
||||
"""Port defining how the application accesses technique data.
|
||||
|
||||
This is a domain contract — implementations live in infrastructure/.
|
||||
The domain layer NEVER imports the implementation.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from typing import NamedTuple, Protocol, runtime_checkable
|
||||
|
||||
from app.domain.entities.technique import TechniqueEntity
|
||||
from app.domain.enums import TechniqueStatus
|
||||
|
||||
|
||||
class TechniqueWithCounts(NamedTuple):
|
||||
"""Pre-aggregated technique data for heatmap/scoring."""
|
||||
|
||||
entity: TechniqueEntity
|
||||
test_count: int
|
||||
validated_test_count: int
|
||||
detection_rule_count: int
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class TechniqueRepository(Protocol):
|
||||
"""Data access contract for techniques (one per aggregate root)."""
|
||||
|
||||
# -- Single-entity access ----------------------------------------------
|
||||
|
||||
def find_by_id(self, technique_id: uuid.UUID) -> TechniqueEntity | None: ...
|
||||
|
||||
def find_by_mitre_id(self, mitre_id: str) -> TechniqueEntity | None: ...
|
||||
|
||||
# -- List access -------------------------------------------------------
|
||||
|
||||
def list_all(
|
||||
self,
|
||||
*,
|
||||
tactic: str | None = None,
|
||||
status: TechniqueStatus | None = None,
|
||||
review_required: bool | None = None,
|
||||
) -> list[TechniqueEntity]: ...
|
||||
|
||||
def list_by_ids(self, ids: list[uuid.UUID]) -> list[TechniqueEntity]: ...
|
||||
|
||||
# -- Batch queries (scoring/heatmap performance) -----------------------
|
||||
|
||||
def count_by_status(self) -> dict[TechniqueStatus, int]: ...
|
||||
|
||||
def find_all_with_test_counts(self) -> list[TechniqueWithCounts]: ...
|
||||
|
||||
# -- Mutations ---------------------------------------------------------
|
||||
|
||||
def save(self, technique: TechniqueEntity) -> TechniqueEntity: ...
|
||||
|
||||
def exists_by_mitre_id(self, mitre_id: str) -> bool: ...
|
||||
52
backend/app/domain/ports/repositories/test_repository.py
Normal file
52
backend/app/domain/ports/repositories/test_repository.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""Port defining how the application accesses test data.
|
||||
|
||||
This is a domain contract — implementations live in infrastructure/.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from typing import Protocol, runtime_checkable
|
||||
|
||||
from app.domain.enums import TestState
|
||||
|
||||
|
||||
class TestRepository(Protocol):
|
||||
"""Data access contract for tests."""
|
||||
|
||||
# -- Single-entity access ----------------------------------------------
|
||||
|
||||
def find_by_id(self, test_id: uuid.UUID) -> object | None:
|
||||
"""Return a Test ORM model by primary key, or None.
|
||||
|
||||
Returns the ORM model directly (not a domain entity) because
|
||||
the TestEntity is constructed at the service layer via
|
||||
``TestEntity.from_orm()``.
|
||||
"""
|
||||
...
|
||||
|
||||
# -- List access -------------------------------------------------------
|
||||
|
||||
def list_by_technique(self, technique_id: uuid.UUID) -> list[object]: ...
|
||||
|
||||
def list_by_state(self, state: TestState) -> list[object]: ...
|
||||
|
||||
def count_by_technique_and_state(
|
||||
self,
|
||||
technique_id: uuid.UUID,
|
||||
) -> dict[TestState, int]:
|
||||
"""Return test counts grouped by state for a single technique."""
|
||||
...
|
||||
|
||||
# -- Batch queries -----------------------------------------------------
|
||||
|
||||
def get_states_and_results_for_technique(
|
||||
self,
|
||||
technique_id: uuid.UUID,
|
||||
) -> list[tuple[str, str | None]]:
|
||||
"""Return (state, detection_result) pairs for all tests of a technique.
|
||||
|
||||
Used by TechniqueEntity.recalculate_status() without loading full
|
||||
test models.
|
||||
"""
|
||||
...
|
||||
Reference in New Issue
Block a user