Criterios de calidad para aplicaciones Angular + Java
Una aplicación full stack se degrada por motivos distintos en cada capa: en el navegador, por renderizados innecesarios e interfaces inaccesibles; en el servidor, por consultas mal dimensionadas y validaciones ausentes. Este artículo reúne cinco criterios de calidad y, para cada uno, muestra el patrón de referencia en Angular 17+ junto a su equivalente en Java 17+ con Spring Boot 3 y Spring Security 6.
Los fragmentos son patrones de referencia: adapta nombres, rutas y modelos al proyecto. La regla que atraviesa los cinco apartados es la misma: la interfaz ayuda, pero la autenticación, la autorización y la integridad de los datos se deciden en el servidor.
| Criterio | Angular | Java / Spring Boot |
|---|---|---|
| Seguridad | Escape por defecto y guards de rutas | Autorización por endpoint y cookies seguras |
| Arquitectura | Estado centralizado y componentes reutilizables | Dominio, servicio y repositorio |
| Rendimiento | Detección de cambios eficiente y listas estables | Consultas paginadas y concurrencia optimista |
| Calidad | Semántica y anuncio accesible de errores | Validación de entrada y operación transaccional |
| Escalabilidad | Carga diferida de pantallas | Sesiones compartidas y pools ajustados |
1. Seguridad
Proteger datos, identidad y operaciones frente a XSS, filtraciones y accesos indebidos. Angular ayuda en la interfaz, pero la autenticación y la autorización deben validarse siempre en Java. Los secretos viven exclusivamente en el servidor, y las sesiones deben usar cookies seguras con protección CSRF.
Angular: escape por defecto, sesión y protección de rutas
// En la plantilla, la interpolación escapa contenido; no uses bypassSecurityTrustHtml.
<p>{{ comentario }}</p>
@Injectable({ providedIn: 'root' })
export class SessionApi {
private http = inject(HttpClient);
me() {
return this.http.get<Usuario>('/api/me', { withCredentials: true });
}
}
export const adminGuard: CanActivateFn = () => {
const auth = inject(AuthService);
const router = inject(Router);
return auth.isAdmin() ? true : router.parseUrl('/403');
};
La llamada con withCredentials: true hace que la petición lleve la cookie de sesión, y el guard evita llegar a una ruta que el usuario no puede usar. Ninguno de los dos sustituye a la comprobación del servidor.
Java: autenticación, autorización, sesión y secreto fuera del código
@Bean
SecurityFilterChain security(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(a -> a
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated())
.formLogin(Customizer.withDefaults()) // CSRF permanece habilitado por defecto
.build();
}
@Value("${PAYMENT_API_KEY}")
private String paymentApiKey; // Inyectar desde variable/gestor de secretos; nunca exponerla al cliente.
La configuración de la cookie de sesión vive en application.properties:
server.servlet.session.cookie.http-only=true
server.servlet.session.cookie.secure=true
server.servlet.session.cookie.same-site=strict
Con http-only el JavaScript no puede leer la cookie, con secure solo viaja por HTTPS y con same-site=strict el navegador no la envía en peticiones cruzadas.
2. Arquitectura
Separar componentes visuales, estado y composición en Angular, y servicios, dominio y persistencia en Java. La interfaz no debe contener reglas de negocio ni acceder directamente a la base de datos. Cada capa expone contratos claros y tiene una responsabilidad definida.
Angular: estado centralizado y componentes reutilizables
@Injectable({ providedIn: 'root' })
export class CartStore {
readonly items = signal<Producto[]>([]);
readonly total = computed(() =>
this.items().reduce((sum, p) => sum + p.precio, 0)
);
add(producto: Producto) {
this.items.update(items => [...items, producto]);
}
}
Java: dominio, servicio y repositorio
interface OrderRepository extends JpaRepository<Order, UUID> {}
@Service
class OrderService {
private final OrderRepository orders;
OrderService(OrderRepository orders) { this.orders = orders; }
@Transactional
Order create(Order order) { return orders.save(order); }
}
El store concentra el estado del carrito en un solo lugar, y OrderService es el único punto que decide cómo se crea un pedido. El repositorio persiste: no conoce la interfaz ni las reglas de presentación.
3. Rendimiento
Reducir trabajo innecesario de renderizado, memoria y carga en el cliente; en el servidor, controlar consultas, concurrencia y latencia. Prioriza mediciones, paginación e índices antes de añadir caché. Evita suscripciones sin liberar y consultas que carguen más datos de los necesarios.
Angular: detección eficiente y seguimiento estable de listas
@Component({
selector: 'app-product-list',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `@for (p of products(); track p.id) {
<app-product-card [product]="p" />
}`
})
export class ProductListComponent {
products = input.required<Producto[]>();
}
Con OnPush el componente solo se reevalúa cuando cambian sus entradas o su estado, y track p.id evita reconstruir la lista completa cuando solo cambia un elemento.
Java: consulta paginada y control de concurrencia optimista
interface OrderRepository extends JpaRepository<Order, UUID> {
Page<Order> findByStatus(Status status, Pageable page);
}
// En la entidad Order:
@Version
private Long version; // Detecta actualizaciones concurrentes en conflicto.
// En el servicio:
Page<Order> page = orders.findByStatus(
Status.PAID, PageRequest.of(0, 50, Sort.by("createdAt").descending())
);
La página pedida limita el tamaño de la respuesta, y @Version obliga a resolver un conflicto cuando dos transacciones escriben sobre el mismo registro. Los dos evitan que el coste crezca sin control.
4. Calidad
La accesibilidad, la experiencia de usuario y la consistencia visual hacen que la interfaz sea usable. En el servidor, las validaciones, las transacciones y las restricciones de datos protegen la integridad; el monitoreo y los controles de salud ayudan a sostener la disponibilidad. La validación del cliente nunca sustituye a la del servidor.
Angular: semántica, etiqueta y anuncio accesible de errores
<label for="email">Correo electrónico</label>
<input id="email" type="email" [formControl]="email"
required aria-describedby="email-error">
<p id="email-error" aria-live="polite">{{ emailError() }}</p>
El label asociado con for da nombre al campo, aria-describedby enlaza la descripción y aria-live="polite" anuncia el error sin interrumpir al usuario.
Java: validación de entrada y operación transaccional
public record CreateOrder(
@NotBlank String sku,
@Positive int quantity
) {}
@PostMapping("/api/orders")
@Transactional
ResponseEntity<Order> create(@Valid @RequestBody CreateOrder request) {
return ResponseEntity.status(HttpStatus.CREATED).body(orderService.create(request));
}
@Valid activa las restricciones declaradas en el record antes de entrar en el método, y @Transactional hace que la operación se confirme completa o no se confirme en absoluto.
5. Escalabilidad
En Angular, dividir la aplicación y cargar módulos o pantallas bajo demanda reduce el peso inicial y ordena los clientes complejos. En Java, distribuir instancias detrás de un balanceador exige servicios sin estado local o sesiones compartidas. La capacidad se dimensiona con métricas, límites de concurrencia y el presupuesto real de la base de datos.
Angular: carga diferida de una pantalla
export const routes: Routes = [
{
path: 'reports',
loadComponent: () => import('./reports.page')
.then(m => m.ReportsPage)
}
];
El chunk de reports solo se solicita cuando alguien entra a esa ruta.
Java y Spring Boot: sesiones compartidas y límites de conexión
# Requiere la dependencia Spring Session Data Redis y Redis externo.
spring.session.store-type=redis
spring.data.redis.host=${REDIS_HOST}
# Ajustar según la capacidad de la base de datos y el número de instancias.
spring.datasource.hikari.maximum-pool-size=20
Con la sesión fuera de la instancia, cualquier nodo detrás del balanceador atiende al mismo usuario, y el tamaño del pool se elige con la capacidad real de la base de datos y el número de instancias en marcha.
Reglas clave
- Los guards de Angular solo mejoran la navegación; Java debe autorizar cada endpoint.
- No guardar claves privadas ni secretos en Angular, variables de entorno del cliente ni repositorios.
- Ajustar cookies, CSRF, CORS, índices, caché y pools según el modelo real de despliegue.
- Verificar el comportamiento con pruebas de seguridad, accesibilidad, carga y concurrencia, sobre el modelo de despliegue real.
© Arturo Juárez Monroy