Guards e Controle de Fluxo
Em sistemas críticos de saúde, a integridade do processo é tão importante quanto a integridade dos dados. Nossa arquitetura utiliza o conceito de Guards (Guardas) em múltiplas camadas para garantir que uma ação só seja executada se todos os pré-requisitos técnicos e clínicos forem atendidos.
Diferente de uma simples validação de formulário, os Guards atuam como "portões" que impedem o sistema de entrar em estados inválidos ou perigosos.
Camadas de Guards na Arquitetura
Dividimos a responsabilidade de proteção em três níveis complementares:
- Guards de Roteamento (Nível de Acesso)
- Guards de Máquina de Estado (Lógica de Negócio)
- Guards de UI (Experiência do Usuário)
- Guards de Efeito Colateral (Saga/Infraestrutura)
1. Guards de Roteamento (Segurança RBAC)
Localizados na camada de Apps, esses guards impedem que o usuário baixe o código de um domínio para o qual não tem permissão. Utilizamos o RequireRoleLayout para orquestrar essa proteção.
Exemplo Clínico:
Um médico pode acessar /medical/records, mas um paciente tentando acessar a mesma URL será bloqueado antes mesmo do componente de prontuário ser montado (Lazy Loading não é disparado).
// Proteção de rota via Guard de Role
<Route element={<RequireRoleLayout role="DOCTOR" />}>
<Route path="/medical/prescriptions" element={<PrescriptionPage />} />
</Route>2. Guards de Máquina de Estado (XState)
Estes são os guards mais críticos, localizados dentro da camada de Modules. Eles garantem que a transição entre estados de um protocolo médico siga regras estritas.
Exemplo: Validação de Teleconsulta
Um médico não pode clicar em "Finalizar Atendimento" se o diagnóstico obrigatório não tiver sido preenchido ou se a prescrição estiver em estado de "Salvando...".
// Exemplo de Guard no XState
{
on: {
FINISH_CONSULTATION: {
target: 'finished',
// O Guard impede a transição se a condição for falsa
cond: (context) => context.hasDiagnosis && context.isPrescriptionSaved
}
}
}3. Guards de UI (Component Level)
Na camada de Shared ou Domains/UI, utilizamos guards para controlar a interatividade. Eles geralmente refletem o estado da máquina de estados ou permissões de contexto.
- Guards de Habilitação: Botões que permanecem desabilitados (
disabled) enquanto um processo assíncrono crítico ocorre. - Guards de Visibilidade: Elementos que só aparecem se o usuário possuir um "Consentimento" assinado.
const FinishButton = () => {
const { canFinish } = useConsultationGuard(); // Hook que consulta a State Machine
return (
<Button
disabled={!canFinish}
tooltip={!canFinish ? "Preencha o diagnóstico antes de finalizar" : ""}
>
Finalizar Atendimento
</Button>
);
};4. Guards de Efeito Colateral (Sagas)
Na camada de Modules/Store, os Sagas atuam como o último guard antes de uma chamada de API ou de um evento de domínio ser disparado. Eles validam se o estado global da aplicação é consistente para aquela operação.
Por que usar guards nos Sagas?
Para evitar "Race Conditions" (Condições de Corrida). Se um usuário clicar duas vezes rapidamente em "Salvar", o Saga utiliza guards (como takeLatest ou verificações de loading) para garantir que apenas uma operação seja processada.
// packages/modules/medical-record/store/sagas.ts
import { select, call, put } from 'redux-saga/effects';
import { selectIsSaving } from './selectors';
import { actions } from './slice';
function* handleSaveMedicalRecord(action) {
// Guard de Reentrada
const isSaving = yield select(selectIsSaving);
if (isSaving) {
console.warn('Operação de salvamento já em curso. Ignorando...');
return;
}
// Guard de Validação de Dados Críticos
if (!action.payload.diagnosisCode) {
yield put(actions.setError('O código CID é obrigatório para salvar o prontuário.'));
return;
}
try {
yield put(actions.setSaving(true));
yield call(api.save, action.payload);
yield put(actions.saveSuccess());
} catch (err) {
yield put(actions.saveFailure(err.message));
} finally {
yield put(actions.setSaving(false));
}
}Implementação de Guard de Roteamento (RequireRoleLayout)
O guard de roteamento é um componente de alta ordem que protege galhos inteiros da árvore de rotas.
// infra/auth/RequireRoleLayout.tsx
import { Navigate, Outlet } from 'react-router-dom';
import { useAuth } from './hooks';
interface Props {
role: 'DOCTOR' | 'PATIENT' | 'ADMIN';
}
export const RequireRoleLayout = ({ role }: Props) => {
const { user, isAuthenticated, loading } = useAuth();
if (loading) return <LoadingSpinner />;
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
if (user.role !== role) {
return <Navigate to="/unauthorized" replace />;
}
// Se passou em todos os guards, renderiza as rotas filhas
return <Outlet />;
};Matriz de Responsabilidade de Guards
| Tipo de Guard | Localização | Objetivo Principal | Quando falha... |
|---|---|---|---|
| Roteamento | Apps | Segurança e Autorização (RBAC) | Redireciona para Login/403 |
| State Machine | Modules | Integridade do Processo Clínico | Impede a mudança de tela/estado |
| UI | Shared/UI | Feedback ao Usuário | Desabilita botões/inputs |
| Saga | Modules/Store | Consistência de Dados e API | Cancela a requisição/log de erro |
Conclusão
O uso sistemático de Guards em nossa arquitetura garante que a aplicação seja "resiliente a erros de operação". Em um contexto médico, onde a agilidade é necessária, os guards protegem o profissional de saúde de cometer erros acidentais no fluxo do sistema, garantindo que o software siga rigorosamente os protocolos estabelecidos.