Pular para conteúdo

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)