maestro-bundle 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -0
- package/package.json +25 -0
- package/src/cli.mjs +212 -0
- package/templates/bundle-ai-agents/.spec/constitution.md +33 -0
- package/templates/bundle-ai-agents/AGENTS.md +140 -0
- package/templates/bundle-ai-agents/skills/agent-orchestration/SKILL.md +132 -0
- package/templates/bundle-ai-agents/skills/api-design/SKILL.md +100 -0
- package/templates/bundle-ai-agents/skills/clean-architecture/SKILL.md +99 -0
- package/templates/bundle-ai-agents/skills/context-engineering/SKILL.md +98 -0
- package/templates/bundle-ai-agents/skills/database-modeling/SKILL.md +59 -0
- package/templates/bundle-ai-agents/skills/docker-containerization/SKILL.md +114 -0
- package/templates/bundle-ai-agents/skills/eval-testing/SKILL.md +115 -0
- package/templates/bundle-ai-agents/skills/memory-management/SKILL.md +106 -0
- package/templates/bundle-ai-agents/skills/prompt-engineering/SKILL.md +66 -0
- package/templates/bundle-ai-agents/skills/rag-pipeline/SKILL.md +128 -0
- package/templates/bundle-ai-agents/skills/testing-strategy/SKILL.md +95 -0
- package/templates/bundle-base/AGENTS.md +118 -0
- package/templates/bundle-base/skills/branch-strategy/SKILL.md +42 -0
- package/templates/bundle-base/skills/code-review/SKILL.md +54 -0
- package/templates/bundle-base/skills/commit-pattern/SKILL.md +58 -0
- package/templates/bundle-data-pipeline/.spec/constitution.md +32 -0
- package/templates/bundle-data-pipeline/AGENTS.md +115 -0
- package/templates/bundle-data-pipeline/skills/data-preprocessing/SKILL.md +75 -0
- package/templates/bundle-data-pipeline/skills/docker-containerization/SKILL.md +114 -0
- package/templates/bundle-data-pipeline/skills/feature-engineering/SKILL.md +76 -0
- package/templates/bundle-data-pipeline/skills/mlops-pipeline/SKILL.md +77 -0
- package/templates/bundle-data-pipeline/skills/model-training/SKILL.md +68 -0
- package/templates/bundle-data-pipeline/skills/rag-pipeline/SKILL.md +128 -0
- package/templates/bundle-frontend-spa/.spec/constitution.md +32 -0
- package/templates/bundle-frontend-spa/AGENTS.md +107 -0
- package/templates/bundle-frontend-spa/skills/authentication/SKILL.md +90 -0
- package/templates/bundle-frontend-spa/skills/component-design/SKILL.md +115 -0
- package/templates/bundle-frontend-spa/skills/e2e-testing/SKILL.md +101 -0
- package/templates/bundle-frontend-spa/skills/integration-api/SKILL.md +95 -0
- package/templates/bundle-frontend-spa/skills/react-patterns/SKILL.md +130 -0
- package/templates/bundle-frontend-spa/skills/responsive-layout/SKILL.md +65 -0
- package/templates/bundle-frontend-spa/skills/state-management/SKILL.md +86 -0
- package/templates/bundle-jhipster-microservices/.spec/constitution.md +37 -0
- package/templates/bundle-jhipster-microservices/AGENTS.md +307 -0
- package/templates/bundle-jhipster-microservices/skills/ci-cd-pipeline/SKILL.md +112 -0
- package/templates/bundle-jhipster-microservices/skills/clean-architecture/SKILL.md +99 -0
- package/templates/bundle-jhipster-microservices/skills/ddd-tactical/SKILL.md +138 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-angular/SKILL.md +97 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-docker-k8s/SKILL.md +183 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-entities/SKILL.md +87 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-gateway/SKILL.md +96 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-kafka/SKILL.md +145 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-registry/SKILL.md +83 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-service/SKILL.md +131 -0
- package/templates/bundle-jhipster-microservices/skills/testing-strategy/SKILL.md +95 -0
- package/templates/bundle-jhipster-monorepo/.spec/constitution.md +32 -0
- package/templates/bundle-jhipster-monorepo/AGENTS.md +227 -0
- package/templates/bundle-jhipster-monorepo/skills/clean-architecture/SKILL.md +99 -0
- package/templates/bundle-jhipster-monorepo/skills/ddd-tactical/SKILL.md +138 -0
- package/templates/bundle-jhipster-monorepo/skills/jhipster-angular/SKILL.md +166 -0
- package/templates/bundle-jhipster-monorepo/skills/jhipster-entities/SKILL.md +141 -0
- package/templates/bundle-jhipster-monorepo/skills/jhipster-liquibase/SKILL.md +95 -0
- package/templates/bundle-jhipster-monorepo/skills/jhipster-security/SKILL.md +89 -0
- package/templates/bundle-jhipster-monorepo/skills/jhipster-spring/SKILL.md +155 -0
- package/templates/bundle-jhipster-monorepo/skills/testing-strategy/SKILL.md +95 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ddd-tactical
|
|
3
|
+
description: Implementar padrões táticos do DDD - Entities, Value Objects, Aggregates, Domain Events, Repositories e Domain Services. Use quando for modelar domínio, criar entidades ricas, ou aplicar DDD no código.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# DDD Tático
|
|
7
|
+
|
|
8
|
+
## Value Objects
|
|
9
|
+
|
|
10
|
+
Imutáveis, comparados por valor, contêm validação.
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class Email:
|
|
15
|
+
value: str
|
|
16
|
+
|
|
17
|
+
def __post_init__(self):
|
|
18
|
+
if not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', self.value):
|
|
19
|
+
raise InvalidEmailException(self.value)
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class ComplianceScore:
|
|
23
|
+
value: float
|
|
24
|
+
|
|
25
|
+
def __post_init__(self):
|
|
26
|
+
if not 0 <= self.value <= 100:
|
|
27
|
+
raise ValueError("Score deve ser entre 0 e 100")
|
|
28
|
+
|
|
29
|
+
def is_passing(self) -> bool:
|
|
30
|
+
return self.value >= 80.0
|
|
31
|
+
|
|
32
|
+
def __str__(self) -> str:
|
|
33
|
+
return f"{self.value:.1f}%"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Entities
|
|
37
|
+
|
|
38
|
+
Identidade única, mutáveis, contêm comportamento.
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
class Task:
|
|
42
|
+
def __init__(self, id: TaskId, description: str, agent_type: AgentType):
|
|
43
|
+
self._id = id
|
|
44
|
+
self._description = description
|
|
45
|
+
self._agent_type = agent_type
|
|
46
|
+
self._status = TaskStatus.PENDING
|
|
47
|
+
self._started_at: datetime | None = None
|
|
48
|
+
self._completed_at: datetime | None = None
|
|
49
|
+
|
|
50
|
+
def start(self) -> None:
|
|
51
|
+
if self._status != TaskStatus.PENDING:
|
|
52
|
+
raise TaskNotPendingException(self._id)
|
|
53
|
+
self._status = TaskStatus.IN_PROGRESS
|
|
54
|
+
self._started_at = datetime.now()
|
|
55
|
+
|
|
56
|
+
def complete(self, result: TaskResult) -> None:
|
|
57
|
+
if self._status != TaskStatus.IN_PROGRESS:
|
|
58
|
+
raise TaskNotInProgressException(self._id)
|
|
59
|
+
self._status = TaskStatus.COMPLETED
|
|
60
|
+
self._completed_at = datetime.now()
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def duration(self) -> timedelta | None:
|
|
64
|
+
if self._started_at and self._completed_at:
|
|
65
|
+
return self._completed_at - self._started_at
|
|
66
|
+
return None
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Aggregates
|
|
70
|
+
|
|
71
|
+
Cluster de entities com uma raiz que protege invariantes.
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
class Demand: # Aggregate Root
|
|
75
|
+
"""Demand é a raiz. Tasks só são acessadas via Demand."""
|
|
76
|
+
|
|
77
|
+
def add_task(self, task: Task) -> None:
|
|
78
|
+
if len(self._tasks) >= 20:
|
|
79
|
+
raise TooManyTasksException("Máximo 20 tasks por demanda")
|
|
80
|
+
self._tasks.append(task)
|
|
81
|
+
|
|
82
|
+
def start_task(self, task_id: TaskId) -> None:
|
|
83
|
+
task = self._find_task(task_id)
|
|
84
|
+
# Verificar invariante: não ter mais de 3 tasks simultâneas
|
|
85
|
+
active = [t for t in self._tasks if t.status == TaskStatus.IN_PROGRESS]
|
|
86
|
+
if len(active) >= 3:
|
|
87
|
+
raise TooManyActiveTasksException()
|
|
88
|
+
task.start()
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Domain Events
|
|
92
|
+
|
|
93
|
+
Notificam que algo aconteceu no domínio.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
@dataclass(frozen=True)
|
|
97
|
+
class DomainEvent:
|
|
98
|
+
occurred_at: datetime = field(default_factory=datetime.now)
|
|
99
|
+
|
|
100
|
+
@dataclass(frozen=True)
|
|
101
|
+
class TaskCompleted(DomainEvent):
|
|
102
|
+
task_id: TaskId
|
|
103
|
+
agent_id: AgentId
|
|
104
|
+
branch_name: str
|
|
105
|
+
duration_seconds: float
|
|
106
|
+
|
|
107
|
+
@dataclass(frozen=True)
|
|
108
|
+
class ComplianceViolationDetected(DomainEvent):
|
|
109
|
+
agent_id: AgentId
|
|
110
|
+
bundle_name: str
|
|
111
|
+
violations: list[str]
|
|
112
|
+
severity: str # "warning" | "critical"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Domain Services
|
|
116
|
+
|
|
117
|
+
Lógica que não pertence a uma única entidade.
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
class TeamRecruitmentService:
|
|
121
|
+
def recruit_for_demand(self, demand: Demand, available: list[Agent]) -> AgentTeam:
|
|
122
|
+
complexity = demand.assess_complexity()
|
|
123
|
+
required = self._determine_roles(complexity)
|
|
124
|
+
team = AgentTeam()
|
|
125
|
+
for role in required:
|
|
126
|
+
agent = next((a for a in available if a.type == role and a.is_available), None)
|
|
127
|
+
if not agent:
|
|
128
|
+
raise NoAvailableAgentException(role)
|
|
129
|
+
team.add(agent)
|
|
130
|
+
return team
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Ordem de prioridade para regras
|
|
134
|
+
|
|
135
|
+
1. **Value Object** — validação e comportamento simples
|
|
136
|
+
2. **Entity** — regras que envolvem estado da entidade
|
|
137
|
+
3. **Domain Service** — regras que envolvem múltiplas entidades
|
|
138
|
+
4. **Use Case** — orquestração (NÃO contém regras)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jhipster-angular
|
|
3
|
+
description: Desenvolver frontend Angular no Gateway JHipster que consome múltiplos microsserviços. Use quando precisar criar telas que agregam dados de vários serviços, ou customizar o frontend do Gateway.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# JHipster Angular — Microservices
|
|
7
|
+
|
|
8
|
+
## Diferença do Monorepo
|
|
9
|
+
|
|
10
|
+
No microservices, o Angular fica no **Gateway**. Ele não sabe que existem múltiplos serviços — o Gateway roteia transparentemente.
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
Browser → Gateway:8080/api/demands → demand-service:8081/api/demands
|
|
14
|
+
Browser → Gateway:8080/api/agents → agent-service:8082/api/agents
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Do ponto de vista do Angular, tudo é uma API só.
|
|
18
|
+
|
|
19
|
+
## Services que agregam dados
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// dashboard.service.ts — agrega dados de múltiplos serviços (via gateway)
|
|
23
|
+
@Injectable({ providedIn: 'root' })
|
|
24
|
+
export class DashboardService {
|
|
25
|
+
constructor(
|
|
26
|
+
private demandService: DemandService,
|
|
27
|
+
private agentService: AgentService,
|
|
28
|
+
private trackingService: TrackingService,
|
|
29
|
+
) {}
|
|
30
|
+
|
|
31
|
+
getMetrics(): Observable<DashboardMetrics> {
|
|
32
|
+
return forkJoin({
|
|
33
|
+
activeDemands: this.demandService.countByStatus('IN_PROGRESS'),
|
|
34
|
+
activeAgents: this.agentService.countByStatus('BUSY'),
|
|
35
|
+
completedToday: this.trackingService.getCompletedToday(),
|
|
36
|
+
complianceRate: this.trackingService.getComplianceRate(),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Tratamento de falha parcial
|
|
43
|
+
|
|
44
|
+
Se um serviço estiver fora do ar, mostrar o que conseguir:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
getMetrics(): Observable<DashboardMetrics> {
|
|
48
|
+
return forkJoin({
|
|
49
|
+
activeDemands: this.demandService.countByStatus('IN_PROGRESS').pipe(
|
|
50
|
+
catchError(() => of({ count: -1 })) // -1 = indisponível
|
|
51
|
+
),
|
|
52
|
+
activeAgents: this.agentService.countByStatus('BUSY').pipe(
|
|
53
|
+
catchError(() => of({ count: -1 }))
|
|
54
|
+
),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```html
|
|
60
|
+
<div *ngIf="metrics.activeDemands.count >= 0; else unavailable">
|
|
61
|
+
{{ metrics.activeDemands.count }}
|
|
62
|
+
</div>
|
|
63
|
+
<ng-template #unavailable>
|
|
64
|
+
<span class="text-muted">Indisponível</span>
|
|
65
|
+
</ng-template>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## WebSocket para real-time
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// No Gateway, conectar via STOMP
|
|
72
|
+
@Injectable({ providedIn: 'root' })
|
|
73
|
+
export class WebSocketService {
|
|
74
|
+
private stompClient: RxStomp;
|
|
75
|
+
|
|
76
|
+
connect(): void {
|
|
77
|
+
this.stompClient = new RxStomp();
|
|
78
|
+
this.stompClient.configure({
|
|
79
|
+
brokerURL: `ws://${location.host}/websocket`,
|
|
80
|
+
});
|
|
81
|
+
this.stompClient.activate();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
onAgentEvent(): Observable<TrackingEvent> {
|
|
85
|
+
return this.stompClient.watch('/topic/tracking').pipe(
|
|
86
|
+
map(msg => JSON.parse(msg.body))
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Regras
|
|
93
|
+
|
|
94
|
+
- Frontend no Gateway, não nos serviços
|
|
95
|
+
- Tratar falha de serviços individuais gracefully
|
|
96
|
+
- Usar `forkJoin` para agregar, `catchError` para resiliência
|
|
97
|
+
- Lazy loading por módulo/feature
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jhipster-docker-k8s
|
|
3
|
+
description: Deploy de microsserviços JHipster com Docker Compose (dev) e Kubernetes/K3s (prod). Use quando precisar containerizar serviços JHipster, criar docker-compose, ou fazer deploy em K8s.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Docker & Kubernetes para JHipster Microservices
|
|
7
|
+
|
|
8
|
+
## Docker Compose — Dev
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# Gerar docker-compose via JHipster
|
|
12
|
+
jhipster docker-compose
|
|
13
|
+
|
|
14
|
+
# Ou usar o gerado pelo JDL deployment
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```yaml
|
|
18
|
+
# docker-compose/docker-compose.yml
|
|
19
|
+
services:
|
|
20
|
+
gateway:
|
|
21
|
+
image: gateway:latest
|
|
22
|
+
ports:
|
|
23
|
+
- "8080:8080"
|
|
24
|
+
environment:
|
|
25
|
+
- SPRING_PROFILES_ACTIVE=dev
|
|
26
|
+
- SPRING_CLOUD_CONSUL_HOST=consul
|
|
27
|
+
depends_on:
|
|
28
|
+
- consul
|
|
29
|
+
- keycloak
|
|
30
|
+
|
|
31
|
+
demand-service:
|
|
32
|
+
image: demand-service:latest
|
|
33
|
+
environment:
|
|
34
|
+
- SPRING_PROFILES_ACTIVE=dev
|
|
35
|
+
- SPRING_CLOUD_CONSUL_HOST=consul
|
|
36
|
+
- SPRING_DATASOURCE_URL=jdbc:postgresql://demand-db:5432/demand
|
|
37
|
+
depends_on:
|
|
38
|
+
- demand-db
|
|
39
|
+
- consul
|
|
40
|
+
- kafka
|
|
41
|
+
|
|
42
|
+
demand-db:
|
|
43
|
+
image: postgres:16
|
|
44
|
+
environment:
|
|
45
|
+
POSTGRES_DB: demand
|
|
46
|
+
POSTGRES_USER: demand
|
|
47
|
+
POSTGRES_PASSWORD: demand
|
|
48
|
+
|
|
49
|
+
agent-service:
|
|
50
|
+
image: agent-service:latest
|
|
51
|
+
environment:
|
|
52
|
+
- SPRING_PROFILES_ACTIVE=dev
|
|
53
|
+
- SPRING_CLOUD_CONSUL_HOST=consul
|
|
54
|
+
- SPRING_DATASOURCE_URL=jdbc:postgresql://agent-db:5432/agent
|
|
55
|
+
depends_on:
|
|
56
|
+
- agent-db
|
|
57
|
+
- consul
|
|
58
|
+
- kafka
|
|
59
|
+
|
|
60
|
+
agent-db:
|
|
61
|
+
image: postgres:16
|
|
62
|
+
environment:
|
|
63
|
+
POSTGRES_DB: agent
|
|
64
|
+
POSTGRES_USER: agent
|
|
65
|
+
POSTGRES_PASSWORD: agent
|
|
66
|
+
|
|
67
|
+
consul:
|
|
68
|
+
image: consul:1.15
|
|
69
|
+
ports:
|
|
70
|
+
- "8500:8500"
|
|
71
|
+
command: agent -server -bootstrap -ui -client=0.0.0.0
|
|
72
|
+
|
|
73
|
+
kafka:
|
|
74
|
+
image: confluentinc/cp-kafka:7.5.0
|
|
75
|
+
depends_on:
|
|
76
|
+
- zookeeper
|
|
77
|
+
ports:
|
|
78
|
+
- "9092:9092"
|
|
79
|
+
environment:
|
|
80
|
+
KAFKA_BROKER_ID: 1
|
|
81
|
+
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
|
|
82
|
+
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092
|
|
83
|
+
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
|
|
84
|
+
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
|
|
85
|
+
|
|
86
|
+
zookeeper:
|
|
87
|
+
image: confluentinc/cp-zookeeper:7.5.0
|
|
88
|
+
environment:
|
|
89
|
+
ZOOKEEPER_CLIENT_PORT: 2181
|
|
90
|
+
|
|
91
|
+
keycloak:
|
|
92
|
+
image: quay.io/keycloak/keycloak:23.0
|
|
93
|
+
ports:
|
|
94
|
+
- "9080:8080"
|
|
95
|
+
environment:
|
|
96
|
+
KEYCLOAK_ADMIN: admin
|
|
97
|
+
KEYCLOAK_ADMIN_PASSWORD: admin
|
|
98
|
+
command: start-dev --import-realm
|
|
99
|
+
volumes:
|
|
100
|
+
- ./keycloak-realm.json:/opt/keycloak/data/import/realm.json
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Kubernetes — Prod
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# Gerar manifests via JHipster
|
|
107
|
+
jhipster kubernetes
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Deployment por serviço
|
|
111
|
+
```yaml
|
|
112
|
+
apiVersion: apps/v1
|
|
113
|
+
kind: Deployment
|
|
114
|
+
metadata:
|
|
115
|
+
name: demand-service
|
|
116
|
+
namespace: maestro
|
|
117
|
+
spec:
|
|
118
|
+
replicas: 2
|
|
119
|
+
selector:
|
|
120
|
+
matchLabels:
|
|
121
|
+
app: demand-service
|
|
122
|
+
template:
|
|
123
|
+
spec:
|
|
124
|
+
containers:
|
|
125
|
+
- name: demand-service
|
|
126
|
+
image: registry.local/demand-service:1.0.0
|
|
127
|
+
ports:
|
|
128
|
+
- containerPort: 8081
|
|
129
|
+
resources:
|
|
130
|
+
requests:
|
|
131
|
+
memory: "512Mi"
|
|
132
|
+
cpu: "500m"
|
|
133
|
+
limits:
|
|
134
|
+
memory: "1Gi"
|
|
135
|
+
cpu: "1000m"
|
|
136
|
+
readinessProbe:
|
|
137
|
+
httpGet:
|
|
138
|
+
path: /management/health/readiness
|
|
139
|
+
port: 8081
|
|
140
|
+
initialDelaySeconds: 30
|
|
141
|
+
livenessProbe:
|
|
142
|
+
httpGet:
|
|
143
|
+
path: /management/health/liveness
|
|
144
|
+
port: 8081
|
|
145
|
+
initialDelaySeconds: 60
|
|
146
|
+
env:
|
|
147
|
+
- name: SPRING_PROFILES_ACTIVE
|
|
148
|
+
value: prod
|
|
149
|
+
- name: SPRING_DATASOURCE_URL
|
|
150
|
+
valueFrom:
|
|
151
|
+
secretKeyRef:
|
|
152
|
+
name: demand-service-db
|
|
153
|
+
key: url
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Build de imagens
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# Build com Jib (padrão JHipster, sem Docker daemon)
|
|
160
|
+
./mvnw -ntp verify -DskipTests jib:dockerBuild -Pprod
|
|
161
|
+
|
|
162
|
+
# Ou com Docker
|
|
163
|
+
docker build -t demand-service:1.0.0 .
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Comandos úteis
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# Dev: subir tudo
|
|
170
|
+
docker-compose -f docker-compose/docker-compose.yml up -d
|
|
171
|
+
|
|
172
|
+
# Dev: ver logs de um serviço
|
|
173
|
+
docker-compose logs -f demand-service
|
|
174
|
+
|
|
175
|
+
# Prod: deploy
|
|
176
|
+
kubectl apply -f k8s/
|
|
177
|
+
|
|
178
|
+
# Prod: rollback
|
|
179
|
+
kubectl rollout undo deployment/demand-service -n maestro
|
|
180
|
+
|
|
181
|
+
# Prod: escalar
|
|
182
|
+
kubectl scale deployment demand-service --replicas=3 -n maestro
|
|
183
|
+
```
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jhipster-entities
|
|
3
|
+
description: Criar e gerenciar entidades JHipster com JDL para microsserviços, incluindo relações cross-service e DTOs compartilhados. Use quando precisar criar entidades, definir modelo de dados, ou gerar código em microsserviços.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# JHipster Entities — Microservices
|
|
7
|
+
|
|
8
|
+
## Regra fundamental
|
|
9
|
+
|
|
10
|
+
Em microsserviços, **entidades pertencem a um único serviço**. Relacionamentos cross-service usam IDs, não foreign keys.
|
|
11
|
+
|
|
12
|
+
## JDL com microservice assignment
|
|
13
|
+
|
|
14
|
+
```jdl
|
|
15
|
+
/* demand-service entities */
|
|
16
|
+
entity Demand {
|
|
17
|
+
description TextBlob required
|
|
18
|
+
status DemandStatus required
|
|
19
|
+
priority Priority required
|
|
20
|
+
createdAt Instant required
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
entity Task {
|
|
24
|
+
description String required maxlength(500)
|
|
25
|
+
status TaskStatus required
|
|
26
|
+
agentId Long // ID do agente (outro serviço), não FK
|
|
27
|
+
branchName String
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
relationship OneToMany {
|
|
31
|
+
Demand{tasks} to Task{demand required}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
microservice Demand, Task with demandService
|
|
35
|
+
|
|
36
|
+
/* agent-service entities */
|
|
37
|
+
entity Agent {
|
|
38
|
+
name String required
|
|
39
|
+
type AgentType required
|
|
40
|
+
status AgentStatus required
|
|
41
|
+
worktreePath String
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
entity AgentTeam {
|
|
45
|
+
demandId Long required // ID da demand (outro serviço), não FK
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
relationship ManyToMany {
|
|
49
|
+
AgentTeam{agents} to Agent
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
microservice Agent, AgentTeam with agentService
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Comunicação cross-service
|
|
56
|
+
|
|
57
|
+
Quando `Task` referencia `Agent` (outro serviço):
|
|
58
|
+
|
|
59
|
+
```java
|
|
60
|
+
// NO demand-service — Task.java
|
|
61
|
+
@Entity
|
|
62
|
+
public class Task {
|
|
63
|
+
// ...
|
|
64
|
+
private Long agentId; // Apenas o ID, não a entidade Agent
|
|
65
|
+
|
|
66
|
+
// Para obter dados do Agent, chamar agent-service via Feign
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// NO demand-service — DemandService
|
|
70
|
+
@Service
|
|
71
|
+
public class DemandServiceImpl {
|
|
72
|
+
private final AgentServiceClient agentClient;
|
|
73
|
+
|
|
74
|
+
public TaskDetailDTO getTaskDetail(Long taskId) {
|
|
75
|
+
Task task = taskRepository.findById(taskId).orElseThrow();
|
|
76
|
+
AgentDTO agent = agentClient.getAgent(task.getAgentId()); // Feign call
|
|
77
|
+
return new TaskDetailDTO(task, agent);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Regras
|
|
83
|
+
|
|
84
|
+
- Entidade pertence a UM serviço
|
|
85
|
+
- Cross-service: guardar apenas o ID, nunca FK real
|
|
86
|
+
- Para exibir dados de outro serviço: Feign Client ou evento Kafka
|
|
87
|
+
- Nunca compartilhar entities entre serviços (duplicar DTOs se necessário)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jhipster-gateway
|
|
3
|
+
description: Configurar o JHipster Gateway com Spring Cloud Gateway, roteamento, rate limiting e frontend Angular. Use quando precisar configurar rotas do gateway, load balancing, ou CORS entre serviços.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# JHipster Gateway
|
|
7
|
+
|
|
8
|
+
## O que é
|
|
9
|
+
|
|
10
|
+
O Gateway é o ponto de entrada único. Ele roteia requests para os microsserviços, serve o frontend Angular e gerencia autenticação.
|
|
11
|
+
|
|
12
|
+
## Roteamento
|
|
13
|
+
|
|
14
|
+
```yaml
|
|
15
|
+
# application.yml
|
|
16
|
+
spring:
|
|
17
|
+
cloud:
|
|
18
|
+
gateway:
|
|
19
|
+
routes:
|
|
20
|
+
- id: demand-service
|
|
21
|
+
uri: lb://demand-service
|
|
22
|
+
predicates:
|
|
23
|
+
- Path=/api/demands/**,/api/tasks/**
|
|
24
|
+
- id: agent-service
|
|
25
|
+
uri: lb://agent-service
|
|
26
|
+
predicates:
|
|
27
|
+
- Path=/api/agents/**,/api/teams/**
|
|
28
|
+
- id: tracking-service
|
|
29
|
+
uri: lb://tracking-service
|
|
30
|
+
predicates:
|
|
31
|
+
- Path=/api/tracking/**,/api/events/**
|
|
32
|
+
default-filters:
|
|
33
|
+
- TokenRelay
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Rate Limiting no Gateway
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
spring:
|
|
40
|
+
cloud:
|
|
41
|
+
gateway:
|
|
42
|
+
routes:
|
|
43
|
+
- id: demand-service
|
|
44
|
+
uri: lb://demand-service
|
|
45
|
+
predicates:
|
|
46
|
+
- Path=/api/demands/**
|
|
47
|
+
filters:
|
|
48
|
+
- name: RequestRateLimiter
|
|
49
|
+
args:
|
|
50
|
+
redis-rate-limiter.replenishRate: 10
|
|
51
|
+
redis-rate-limiter.burstCapacity: 20
|
|
52
|
+
key-resolver: "#{@userKeyResolver}"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```java
|
|
56
|
+
@Bean
|
|
57
|
+
public KeyResolver userKeyResolver() {
|
|
58
|
+
return exchange -> Mono.just(
|
|
59
|
+
exchange.getRequest().getHeaders().getFirst("Authorization") != null
|
|
60
|
+
? exchange.getRequest().getHeaders().getFirst("Authorization")
|
|
61
|
+
: exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## CORS
|
|
67
|
+
|
|
68
|
+
```java
|
|
69
|
+
@Bean
|
|
70
|
+
public CorsWebFilter corsFilter() {
|
|
71
|
+
CorsConfiguration config = new CorsConfiguration();
|
|
72
|
+
config.setAllowedOrigins(List.of("http://localhost:4200", "https://maestro.local"));
|
|
73
|
+
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
|
|
74
|
+
config.setAllowedHeaders(List.of("*"));
|
|
75
|
+
config.setAllowCredentials(true);
|
|
76
|
+
|
|
77
|
+
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
|
78
|
+
source.registerCorsConfiguration("/api/**", config);
|
|
79
|
+
return new CorsWebFilter(source);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Health check dos serviços
|
|
84
|
+
|
|
85
|
+
```yaml
|
|
86
|
+
management:
|
|
87
|
+
endpoints:
|
|
88
|
+
web:
|
|
89
|
+
exposure:
|
|
90
|
+
include: health,info,gateway
|
|
91
|
+
endpoint:
|
|
92
|
+
gateway:
|
|
93
|
+
enabled: true
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Acessar `GET /actuator/gateway/routes` para ver rotas ativas.
|