Core Capabilities¶
1. Introducao¶
O MIDDAG expoe 6 Core Capabilities (CCs) como bounded contexts com contracts @api. Cada CC resolve um problema transversal que multiplas extensions precisam consumir.
Os contracts vivem em local_middag\framework\contract\. Extensions consomem via injecao de dependencia no construtor. O container resolve a implementacao concreta.
Quando usar¶
- Sua extension precisa segmentar usuarios, verificar compliance, conectar com servicos externos, definir workflow actions, agrupar instancias de cursos ou avaliar condicoes de contexto.
Quando nao usar¶
- Logica especifica de uma unica extension que nao precisa de integracao transversal.
- Operacoes diretas no Moodle que nao passam pelo framework.
2. Quick example¶
use local_middag\framework\contract\segments_interface;
class my_service extends \local_middag\base\service
{
public function __construct(
private readonly segments_interface $segments,
) {}
public function get_audience_count(int $segment_id): int
{
return $this->segments->evaluate($segment_id);
}
}
3. Mapa de Core Capabilities¶
| CC | Dominio | Aggregate | Contract principal | Extension point |
|---|---|---|---|---|
| CC-02 | core.connectors |
connector | connector_interface, connector_registry_interface |
-- |
| CC-03 | core.workflow |
action | workflow_action_interface, workflow_registry_interface |
-- |
| CC-06 | core.instancegroup |
group | instancegroup_interface |
-- |
| CC-07 | core.segments |
segment | segments_interface |
criteria_provider_interface |
| CC-08 | core.compliance |
policy, evaluation | compliance_interface |
-- |
| CC-09 | core.conditions |
filter | conditions_interface |
condition_provider_interface |
Todos os contracts sao @api. Graceful degradation: quando nenhum provider esta registrado, o consumidor trata como "sem restricao" (sem bloqueio).
4. CC-02: Connectors¶
O que faz: Catalogo de tipos de conexao com servicos externos. Gerencia credenciais, health check e binding por extension.
Consumidores tipicos: extensions que integram com APIs externas (Twilio, WooCommerce, BigQuery, Sentry).
connector_interface¶
Implementada pela extension para definir um tipo de connector.
use local_middag\framework\contract\connector_interface;
class twilio_connector implements connector_interface
{
public function get_type(): string
{
return 'twilio';
}
public function get_name(): string
{
return 'Twilio SMS';
}
public function get_extension(): string
{
return 'automessage';
}
public function health_check(): bool
{
// Testar conectividade com a API do Twilio
return $this->client->ping();
}
public function get_credential_fields(): array
{
return [
['key' => 'account_sid', 'label' => 'Account SID', 'type' => 'text'],
['key' => 'auth_token', 'label' => 'Auth Token', 'type' => 'password'],
['key' => 'from_number', 'label' => 'From Number', 'type' => 'text'],
];
}
}
connector_registry_interface¶
Consumida para registrar e recuperar connectors.
| Metodo | Retorno | Descricao |
|---|---|---|
register(connector_interface $connector) |
void |
Registra um connector type |
get(string $type) |
?connector_interface |
Retorna connector por slug |
all() |
array<string, connector_interface> |
Todos os connectors registrados |
for_extension(string $extension) |
list<connector_interface> |
Connectors de uma extension |
Registro: durante register() da extension.
// Na extension
public function register(): void
{
$registry = $this->container->get(connector_registry_interface::class);
$registry->register(new twilio_connector($this->client));
}
5. CC-03: Workflow¶
O que faz: Acoes discretas de trabalho disparadas por formularios, eventos ou tarefas agendadas. A workflow engine avalia triggers e executa as acoes correspondentes.
Consumidores tipicos: customform (pos-submissao), helpdesk (roteamento de tickets), docbuilder (emissao de documentos).
workflow_action_interface¶
Implementada pela extension para definir uma acao de workflow.
use local_middag\framework\contract\workflow_action_interface;
class create_ticket_action implements workflow_action_interface
{
public function get_type(): string
{
return 'create_ticket';
}
public function get_name(): string
{
return 'Create Support Ticket';
}
public function get_extension(): string
{
return 'helpdesk';
}
public function execute(array $payload): bool
{
// Criar ticket com os dados do payload
$this->ticket_service->create(
subject: $payload['subject'],
body: $payload['body'],
user_id: $payload['user_id'],
);
return true;
}
public function get_payload_schema(): array
{
return [
['key' => 'subject', 'label' => 'Subject', 'required' => true],
['key' => 'body', 'label' => 'Body', 'required' => true],
['key' => 'user_id', 'label' => 'User ID', 'required' => true],
['key' => 'priority', 'label' => 'Priority', 'required' => false],
];
}
}
workflow_registry_interface¶
Consumida para registrar e despachar acoes.
| Metodo | Retorno | Descricao |
|---|---|---|
register(workflow_action_interface $action) |
void |
Registra uma acao |
get(string $type) |
?workflow_action_interface |
Retorna acao por slug |
all() |
array<string, workflow_action_interface> |
Todas as acoes |
dispatch(string $type, array $payload) |
bool |
Localiza e executa uma acao pelo type |
Registro: durante boot() da extension.
// Na extension
public function boot(): void
{
$registry = $this->container->get(workflow_registry_interface::class);
$registry->register(new create_ticket_action($this->ticket_service));
}
6. CC-06: Instance Group¶
O que faz: Agrupamento logico e versionamento de cursos/recursos. Extensions usam groups em vez de referencias diretas a cursos, habilitando equivalencia de cursos, re-matricula e logica de progressao.
Consumidores tipicos: learningpath (stages), enrolment (re-enrollment logic).
instancegroup_interface¶
Consumida para resolver grupos de instancias.
use local_middag\framework\contract\instancegroup_interface;
class stage_evaluator extends \local_middag\base\service
{
public function __construct(
private readonly instancegroup_interface $groups,
) {}
public function is_stage_complete(int $group_id, int $user_id): bool
{
return $this->groups->resolve_completion($group_id, $user_id);
}
public function get_current_course(int $group_id): ?item_dto_interface
{
return $this->groups->get_principal_item($group_id);
}
}
| Metodo | Retorno | Descricao |
|---|---|---|
get_principal_item(int $group_id) |
?item_dto_interface |
Item ativo principal do grupo |
resolve_completion(int $group_id, int $user_id) |
bool |
Verifica se algum item ativo foi concluido pelo usuario |
get_active_items(int $group_id) |
list<item_dto_interface> |
Todos os itens ativos do grupo |
7. CC-07: Segments¶
O que faz: Motor de segmentacao de audiencia baseado em regras. Segmentos nao sao listas materializadas -- retornam subqueries SQL para composicao e performance em operacoes em massa.
Consumidores tipicos: automessage (targeting), enrolment (regras de matricula), analytics (filtros de audiencia).
segments_interface¶
Consumida para avaliar segmentos e descobrir criterios disponiveis.
use local_middag\framework\contract\segments_interface;
use local_middag\framework\contract\segment_subquery;
class broadcast_service extends \local_middag\base\service
{
public function __construct(
private readonly segments_interface $segments,
) {}
public function send_to_segment(int $segment_id, string $message): int
{
$subquery = $this->segments->get_subquery($segment_id);
// Usar subquery direto no SQL para eficiencia
$sql = "SELECT * FROM {user} WHERE id IN ({$subquery->sql})";
// ... executar com $subquery->params
return $subquery->count;
}
}
| Metodo | Retorno | Descricao |
|---|---|---|
get_subquery(int $segment_id) |
segment_subquery |
Subquery SQL com parametros e contagem estimada |
evaluate(int $segment_id) |
int |
Avalia segmento e retorna contagem de membros (respeita cache) |
get_criteria_types() |
list<string> |
Tipos de criterio disponiveis (built-in + extensions) |
get_available_fields() |
list<array> |
Campos filtraveis de todos os criteria providers |
segment_subquery (value object)¶
| Propriedade | Tipo | Descricao |
|---|---|---|
sql |
string |
Subquery SQL que retorna user IDs |
params |
list<mixed> |
Parametros bound para a subquery |
count |
int |
Contagem estimada de membros (pode ser cache) |
criteria_provider_interface (extension point)¶
Implementada para adicionar tipos de criterio customizados ao motor de segmentacao.
use local_middag\framework\contract\criteria_provider_interface;
use local_middag\framework\contract\segment_subquery;
class twilio_verified_provider implements criteria_provider_interface
{
public function get_type(): string
{
return 'twilio_verified';
}
public function get_fields(): array
{
return [
[
'name' => 'phone_verified',
'label' => 'Phone Verified',
'category' => 'Communication',
'operators' => ['equals'],
'is_advanced' => false,
],
];
}
public function build_sql(array $config): segment_subquery
{
return new segment_subquery(
sql: "SELECT userid FROM {local_middag_twilio} WHERE verified = :verified",
params: ['verified' => $config['value'] ?? 1],
);
}
public function describe(array $config): string
{
return 'Phone verified via Twilio';
}
public function validate_config(array $config): bool
{
return isset($config['value']);
}
}
| Metodo | Retorno | Descricao |
|---|---|---|
get_type() |
string |
Identificador unico do tipo de criterio |
get_fields() |
list<array> |
Campos filtraveis com operadores e metadata |
build_sql(array $config) |
segment_subquery |
Gera subquery SQL para a configuracao |
describe(array $config) |
string |
Descricao legivel do criterio |
validate_config(array $config) |
bool |
Valida a configuracao antes de persistir |
Registro: durante boot() da extension, via criteria registry.
8. CC-08: Compliance¶
O que faz: Verificacao de compliance de perfil do usuario. Implementacoes checam se o usuario atende todos os requisitos obrigatorios de perfil para um dado contexto Moodle.
Consumidores tipicos: enrolment (gates de matricula), docbuilder (pre-requisitos de emissao de documentos).
Graceful degradation: quando nenhum provider esta registrado, consumidores tratam usuarios como compliant (sem bloqueio).
compliance_interface¶
Consumida para verificar compliance de usuarios.
use local_middag\framework\contract\compliance_interface;
use core\context;
class enrolment_gate extends \local_middag\base\service
{
public function __construct(
private readonly compliance_interface $compliance,
) {}
public function can_enrol(int $user_id, int $course_id): bool
{
$context = context\course::instance($course_id);
return $this->compliance->is_compliant($user_id, $context);
}
public function get_blocking_reasons(int $user_id, int $course_id): array
{
$context = context\course::instance($course_id);
return $this->compliance->get_violations($user_id, $context);
}
}
| Metodo | Retorno | Descricao |
|---|---|---|
is_compliant(int $user_id, context $context) |
bool |
True se o usuario atende todos os requisitos |
get_violations(int $user_id, context $context) |
list<array{field: string, reason: string}> |
Lista de campos ausentes/invalidos |
9. CC-09: Conditions¶
O que faz: Motor de condicoes que avalia regras de inclusao/exclusao contra contextos Moodle (categorias, cursos, tipos de atividade). Funciona como condicoes de exibicao similares a page builders.
Consumidores tipicos: extensions que precisam ativar/desativar funcionalidade por area do site (categorias, cursos, modulos).
Graceful degradation: quando nenhuma regra esta configurada, todos os contextos sao considerados validos.
conditions_interface¶
Consumida para avaliar regras de condicao.
use local_middag\framework\contract\conditions_interface;
use local_middag\framework\contract\condition_rule;
use core\context;
class feature_toggle extends \local_middag\base\service
{
public function __construct(
private readonly conditions_interface $conditions,
) {}
public function is_enabled_for(context $context): bool
{
$rule = new condition_rule(
include_category_ids: [5, 12],
exclude_course_ids: [101],
);
return $this->conditions->matches($rule, $context);
}
}
| Metodo | Retorno | Descricao |
|---|---|---|
matches(condition_rule $rule, context $context) |
bool |
True se o contexto atende a regra |
get_applicable_context_ids(condition_rule $rule) |
list<int> |
IDs de contextos que atendem a regra |
condition_rule (value object)¶
Regra de inclusao/exclusao. Quando uma lista de include esta vazia, todos sao incluidos. Exclude sempre remove.
| Propriedade | Tipo | Descricao |
|---|---|---|
include_category_ids |
list<int> |
Categorias a incluir (vazio = todas) |
exclude_category_ids |
list<int> |
Categorias a excluir |
include_course_ids |
list<int> |
Cursos a incluir (vazio = todos) |
exclude_course_ids |
list<int> |
Cursos a excluir |
include_module_types |
list<string> |
Tipos de atividade a incluir (ex: 'assign', 'quiz') |
exclude_module_types |
list<string> |
Tipos de atividade a excluir |
Metodo: is_empty(): bool -- retorna true se nenhuma condicao esta configurada (match tudo).
condition_provider_interface (extension point)¶
Implementada para adicionar tipos de condicao customizados.
use local_middag\framework\contract\condition_provider_interface;
use local_middag\framework\contract\condition_rule;
use core\context;
class time_range_provider implements condition_provider_interface
{
public function get_type(): string
{
return 'time_range';
}
public function matches(condition_rule $rule, context $context): bool
{
$now = time();
// Logica customizada de avaliacao temporal
return $now >= $this->start && $now <= $this->end;
}
public function get_applicable_context_ids(condition_rule $rule): array
{
// Retornar IDs de contextos que atendem a condicao temporal
return [];
}
}
| Metodo | Retorno | Descricao |
|---|---|---|
get_type() |
string |
Identificador unico do tipo de condicao |
matches(condition_rule $rule, context $context) |
bool |
Avalia se o contexto atende a condicao |
get_applicable_context_ids(condition_rule $rule) |
list<int> |
Todos os contextos que atendem |
Registro: durante boot() da extension.
10. Patterns¶
Pattern 1: Consumir um CC via injecao de dependencia¶
Todas as CCs sao consumidas da mesma forma: type-hint no construtor, o container resolve.
use local_middag\framework\contract\segments_interface;
use local_middag\framework\contract\compliance_interface;
class my_service extends \local_middag\base\service
{
public function __construct(
private readonly segments_interface $segments,
private readonly compliance_interface $compliance,
) {}
}
Pattern 2: Registrar um extension point no boot()¶
Extensions que fornecem providers customizados registram durante boot().
class my_extension extends \local_middag\base\extension
{
public function boot(): void
{
// Registrar criteria provider para segmentacao
$criteria_registry = $this->container->get(criteria_registry_interface::class);
$criteria_registry->register(new my_criteria_provider());
// Registrar workflow action
$workflow_registry = $this->container->get(workflow_registry_interface::class);
$workflow_registry->register(new my_workflow_action());
}
}
Pattern 3: Graceful degradation¶
Quando o provider pode nao estar disponivel, verifique antes de consumir.
public function check_compliance(int $user_id, context $context): bool
{
if (!$this->container->has(compliance_interface::class)) {
// Sem provider registrado -- tratar como compliant
return true;
}
return $this->container->get(compliance_interface::class)
->is_compliant($user_id, $context);
}
11. Anti-patterns¶
| Anti-pattern | Problema | O que fazer |
|---|---|---|
| Importar implementacao concreta do CC | Acopla a extension ao detalhe interno | Use o contract (connector_interface, nao connector_repository) |
| Registrar connector/action fora do lifecycle | Discovery nao encontra | Registre em register() (connectors) ou boot() (actions, providers) |
| Materializar lista de IDs de um segment | Inviavel com milhares de usuarios | Use get_subquery() e compose no SQL |
| Ignorar graceful degradation | Extension quebra quando CC nao tem provider | Verifique container->has() ou trate como "sem restricao" |
| Criar CC customizado dentro de uma extension | CC e bounded context transversal do core | Se a necessidade e especifica, use services/contracts de extension |
| Prefixar identifiers com numero do CC | Redundante, o domain ja qualifica | Use connector, nao cc02_connector |
12. Cross-references¶
- ADR-607: Modelo de subdominios core
- ADR-606: Consolidacao do portfolio de extensions
- ADR-503: Familias de persistencia
- ADR-601: Container e injecao de dependencia
- ADR-602: Lifecycle das extensions (register/boot/compile)