Architecture

System architecture, component interactions, project structure, and key design patterns.

System Context

The admin-service sits between the API Gateway and the platform's two infrastructure systems — HashiCorp Vault and Keycloak. All admin requests arrive through the gateway after JWT validation.

Kafka AdminClient is implemented (list/create/describe topics on Confluent Cloud). Dashed lines now only cover future audit-event publishing and direct user queries.


Internal Component Map


Project Structure

> Text code-highlight
arya-banking-admin-service/
├── .github/
│   └── workflows/
│       └── deploy.yml
├── src/
│   └── main/
│       ├── java/org/arya/banking/admin/
│       │   ├── AryaBankingAdminServiceApplication.java
│       │   ├── annotation/
│       │   │   ├── AdminRestController.java
│       │   │   └── AllowedRoles.java
│       │   ├── config/
│       │   │   ├── ApiProperties.java
│       │   │   ├── AppRoleConfig.java
│       │   │   ├── MethodSecurityConfig.java
│       │   │   ├── SecurityConfig.java
│       │   │   └── VaultConfigs.java
│       │   ├── controller/
│       │   │   ├── ClientCreationController.java
│       │   │   ├── KeyCloakRolesController.java
│       │   │   ├── KafkaAdminController.java
│       │   │   ├── VaultAppRoleController.java
│       │   │   ├── VaultOperationsController.java
│       │   │   └── VaultPolicyController.java
│       │   ├── dto/
│       │   │   ├── AppRole.java
│       │   │   ├── AppRoleResponseDto.java
│       │   │   ├── CreateAppRoleDto.java
│       │   │   ├── KeyCloakClientResponse.java
│       │   │   ├── KeycloakRole.java
│       │   │   ├── TopicCreationDto.java
│       │   │   ├── TopicDescriptionDto.java
│       │   │   ├── TopicListingDto.java
│       │   │   ├── VaultApiResponseDto.java
│       │   │   ├── VaultResponseDto.java
│       │   │   ├── VaultSecret.java
│       │   │   └── VaultSecretDto.java (service + List<VaultSecret>)
│       │   ├── mapper/
│       │   │   ├── BaseMapper.java (abstract class)
│       │   │   ├── KeycloakRoleMapper.java
│       │   │   ├── TopicDescriptionMapper.java
│       │   │   ├── TopicListingMapper.java
│       │   │   └── VaultResponseMapper.java
│       │   └── service/
│       │       ├── AdminClientManager.java
│       │       ├── KeyCloakManager.java
│       │       ├── KeyCloakService.java
│       │       ├── KafkaAdminService.java
│       │       ├── VaultAppRoleService.java
│       │       ├── VaultOperationService.java
│       │       ├── VaultPolicyService.java
│       │       └── impl/
│       │           ├── KeyCloakServiceImpl.java
│       │           ├── KafkaAdminServiceImpl.java
│       │           ├── RolePermissionValidator.java
│       │           ├── VaultAppRoleServiceImpl.java
│       │           ├── VaultOperationServiceImpl.java
│       │           └── VaultPolicyServiceImpl.java
│       └── resources/
│           ├── application.yaml
│           ├── bootstrap.yml
│           ├── admin-service-policy.hcl
│           └── user-service-policy.hcl
└── pom.xml

Key Design Decisions

Operation-Based RBAC

Rather than hardcoding role names in @PreAuthorize annotations, the service externalises the role-to-operation mapping into application.yaml under security.api-roles. This means adding or changing roles for an operation is a config-only change — no code redeployment required.

> Yaml code-highlight
security:
  api-roles:
    create-client:
      - ROLE_ADMIN
    query-realm:
      - ROLE_ADMIN
    vault-ops:
      - ROLE_ADMIN
    kafka-ops:
      - ROLE_ADMIN

The RolePermissionValidator bean reads this map and is referenced via @AllowedRoles (preferred) or SpEL:

> Java code-highlight
@AllowedRoles(KAFKA_OPS) // "kafka-ops"
// or legacy:
@PreAuthorize("@rolePermissionValidator.hasAnyRole(authentication, 'vault-ops')")

Policy-as-Code

Vault HCL policy files (admin-service-policy.hcl, user-service-policy.hcl) are stored in src/main/resources/ and checked in to the repository. The VaultPolicyServiceImpl reads them from the classpath and uploads them to Vault on demand via API. This keeps policy definitions version-controlled alongside the service that owns them.

Common Library Integration

The @ComponentScan in the main class includes org.arya.banking.common, meaning the common library's @Component beans — GlobalExceptionHandler, MetadataInitializer, base mappers — are registered automatically without any explicit import configuration.

Composite @AdminRestController Annotation

To avoid repeating @RestController + @RequestMapping("/api/admin") on every controller, a composed annotation @AdminRestController bundles both. All controllers except ClientCreationController use it.

ClientCreationController declares @RequestMapping(

architecturedesignpatterns