Files
ldpv2/doc/01-TECHNICAL-SETUP.md
2026-02-07 17:51:17 +01:00

21 KiB

LDPv2 - Technical Setup Guide

Overview

This guide provides the technical foundation for the LDPv2 project, including technology stack details, project structure, and initial setup instructions.

Technology Stack

Backend

  • Framework: Spring Boot 3.2.x
  • Language: Java 17 or 21
  • Security: Spring Security 6.x with JWT
  • Persistence:
    • PostgreSQL 16
    • Hibernate 6.x (JPA)
    • HikariCP (connection pooling)
  • Migration: Liquibase 4.x
  • Build Tool: Maven 3.9.x
  • Testing:
    • JUnit 5
    • Mockito
    • Testcontainers (for integration tests)
    • REST Assured (for API tests)
  • Documentation: SpringDoc OpenAPI (Swagger)
  • Validation: Jakarta Bean Validation

Frontend

  • Framework: Angular 18
  • Language: TypeScript 5.x
  • UI Library: Angular Material or PrimeNG (to be decided)
  • HTTP Client: Angular HttpClient
  • State Management: Service-based (or NgRx for complex state)
  • Forms: Reactive Forms
  • Routing: Angular Router
  • Testing:
    • Jasmine/Karma (unit tests)
    • Cypress or Playwright (E2E tests)
  • Build Tool: Angular CLI

DevOps

  • Containerization: Docker
  • Container Orchestration: Docker Compose (dev), Kubernetes (prod)
  • CI/CD: Jenkins, GitLab CI, or GitHub Actions
  • Version Control: Git

Project Structure

Backend Structure

ldpv2-backend/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/ldpv2/
│   │   │       ├── LdpV2Application.java
│   │   │       ├── config/
│   │   │       │   ├── SecurityConfig.java
│   │   │       │   ├── JwtConfig.java
│   │   │       │   └── OpenApiConfig.java
│   │   │       ├── domain/
│   │   │       │   ├── entity/
│   │   │       │   │   ├── BaseEntity.java
│   │   │       │   │   ├── Application.java
│   │   │       │   │   ├── Environment.java
│   │   │       │   │   └── ...
│   │   │       │   └── enums/
│   │   │       │       ├── ApplicationStatus.java
│   │   │       │       └── ExternalDependencyType.java
│   │   │       ├── repository/
│   │   │       │   ├── ApplicationRepository.java
│   │   │       │   ├── EnvironmentRepository.java
│   │   │       │   └── ...
│   │   │       ├── service/
│   │   │       │   ├── ApplicationService.java
│   │   │       │   ├── EnvironmentService.java
│   │   │       │   └── ...
│   │   │       ├── dto/
│   │   │       │   ├── request/
│   │   │       │   │   ├── CreateApplicationRequest.java
│   │   │       │   │   └── ...
│   │   │       │   └── response/
│   │   │       │       ├── ApplicationResponse.java
│   │   │       │       └── ...
│   │   │       ├── controller/
│   │   │       │   ├── ApplicationController.java
│   │   │       │   ├── EnvironmentController.java
│   │   │       │   └── ...
│   │   │       ├── security/
│   │   │       │   ├── JwtTokenProvider.java
│   │   │       │   ├── JwtAuthenticationFilter.java
│   │   │       │   └── UserDetailsServiceImpl.java
│   │   │       └── exception/
│   │   │           ├── GlobalExceptionHandler.java
│   │   │           ├── ResourceNotFoundException.java
│   │   │           └── ...
│   │   └── resources/
│   │       ├── application.yml
│   │       ├── application-dev.yml
│   │       ├── application-prod.yml
│   │       └── db/
│   │           └── changelog/
│   │               ├── db.changelog-master.xml
│   │               ├── v1.0/
│   │               │   ├── 001-create-base-tables.xml
│   │               │   ├── 002-create-application-tables.xml
│   │               │   └── ...
│   │               └── data/
│   │                   └── initial-data.xml
│   └── test/
│       └── java/
│           └── com/ldpv2/
│               ├── integration/
│               │   ├── ApplicationIntegrationTest.java
│               │   └── ...
│               ├── service/
│               │   ├── ApplicationServiceTest.java
│               │   └── ...
│               └── controller/
│                   ├── ApplicationControllerTest.java
│                   └── ...
├── pom.xml
├── Dockerfile
└── docker-compose.yml

Frontend Structure

ldpv2-frontend/
├── src/
│   ├── app/
│   │   ├── core/
│   │   │   ├── auth/
│   │   │   │   ├── auth.service.ts
│   │   │   │   ├── auth.guard.ts
│   │   │   │   ├── jwt.interceptor.ts
│   │   │   │   └── login/
│   │   │   │       ├── login.component.ts
│   │   │   │       ├── login.component.html
│   │   │   │       └── login.component.scss
│   │   │   ├── services/
│   │   │   │   ├── api.service.ts
│   │   │   │   └── error-handler.service.ts
│   │   │   └── interceptors/
│   │   │       ├── jwt.interceptor.ts
│   │   │       └── error.interceptor.ts
│   │   ├── shared/
│   │   │   ├── models/
│   │   │   │   ├── application.model.ts
│   │   │   │   ├── environment.model.ts
│   │   │   │   └── ...
│   │   │   ├── components/
│   │   │   │   ├── header/
│   │   │   │   ├── footer/
│   │   │   │   └── loading-spinner/
│   │   │   └── pipes/
│   │   │       └── date-format.pipe.ts
│   │   ├── features/
│   │   │   ├── applications/
│   │   │   │   ├── application.service.ts
│   │   │   │   ├── application-list/
│   │   │   │   │   ├── application-list.component.ts
│   │   │   │   │   ├── application-list.component.html
│   │   │   │   │   └── application-list.component.scss
│   │   │   │   ├── application-detail/
│   │   │   │   └── application-form/
│   │   │   ├── environments/
│   │   │   │   ├── environment.service.ts
│   │   │   │   └── ...
│   │   │   ├── deployments/
│   │   │   └── business-units/
│   │   ├── app.component.ts
│   │   ├── app.component.html
│   │   ├── app.routes.ts
│   │   └── app.config.ts
│   ├── assets/
│   │   ├── images/
│   │   └── i18n/
│   ├── environments/
│   │   ├── environment.ts
│   │   └── environment.prod.ts
│   ├── styles.scss
│   └── index.html
├── angular.json
├── package.json
├── tsconfig.json
└── Dockerfile

Initial Setup

Prerequisites

  • JDK 17 or 21
  • Node.js 18+ and npm
  • Docker and Docker Compose
  • PostgreSQL 16 (or use Docker)
  • Git
  • IDE (IntelliJ IDEA, VS Code, or similar)

Backend Setup

1. Create Spring Boot Project

# Using Spring Initializr or
# Download from https://start.spring.io with:
# - Spring Boot 3.2.x
# - Dependencies: Web, Security, JPA, PostgreSQL, Liquibase, Validation

2. Configure application.yml

spring:
  application:
    name: ldpv2-backend
  
  datasource:
    url: jdbc:postgresql://localhost:5432/ldpv2
    username: ldpv2_user
    password: ldpv2_password
    driver-class-name: org.postgresql.Driver
    hikari:
      maximum-pool-size: 10
      minimum-idle: 5
  
  jpa:
    hibernate:
      ddl-auto: validate # Let Liquibase handle schema
    show-sql: true
    properties:
      hibernate:
        format_sql: true
        dialect: org.hibernate.dialect.PostgreSQLDialect
  
  liquibase:
    change-log: classpath:db/changelog/db.changelog-master.xml
    enabled: true

jwt:
  secret: ${JWT_SECRET:your-secret-key-change-in-production}
  expiration: 3600000 # 1 hour in milliseconds

server:
  port: 8080
  servlet:
    context-path: /api

logging:
  level:
    com.ldpv2: DEBUG
    org.springframework.security: DEBUG

3. Docker Compose for Development

version: '3.8'

services:
  postgres:
    image: postgres:16-alpine
    container_name: ldpv2-postgres
    environment:
      POSTGRES_DB: ldpv2
      POSTGRES_USER: ldpv2_user
      POSTGRES_PASSWORD: ldpv2_password
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - ldpv2-network

  backend:
    build: ./ldpv2-backend
    container_name: ldpv2-backend
    depends_on:
      - postgres
    environment:
      SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/ldpv2
      SPRING_DATASOURCE_USERNAME: ldpv2_user
      SPRING_DATASOURCE_PASSWORD: ldpv2_password
      JWT_SECRET: development-secret-key
    ports:
      - "8080:8080"
    networks:
      - ldpv2-network

volumes:
  postgres_data:

networks:
  ldpv2-network:
    driver: bridge

Frontend Setup

1. Create Angular Project

npm install -g @angular/cli@18
ng new ldpv2-frontend
cd ldpv2-frontend

2. Install Dependencies

# Angular Material (or PrimeNG)
ng add @angular/material

# Additional dependencies
npm install --save \
  @angular/common \
  @angular/forms \
  rxjs

3. Configure Environment

// src/environments/environment.ts
export const environment = {
  production: false,
  apiUrl: 'http://localhost:8080/api'
};

// src/environments/environment.prod.ts
export const environment = {
  production: true,
  apiUrl: '/api'
};

4. Proxy Configuration for Development

// proxy.conf.json
{
  "/api": {
    "target": "http://localhost:8080",
    "secure": false,
    "changeOrigin": true
  }
}

Update angular.json:

"serve": {
  "builder": "@angular-devkit/build-angular:dev-server",
  "options": {
    "proxyConfig": "proxy.conf.json"
  }
}

Database Schema Management

Liquibase Changelog Structure

<!-- db.changelog-master.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
    xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
    http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">

    <include file="db/changelog/v1.0/001-create-base-tables.xml"/>
    <include file="db/changelog/v1.0/002-create-application-tables.xml"/>
    <include file="db/changelog/v1.0/003-create-deployment-tables.xml"/>
    <!-- Add more as stories are developed -->
    
</databaseChangeLog>

Example Migration File

<!-- 001-create-base-tables.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
    xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
    http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">

    <changeSet id="001-create-base-entities" author="ldpv2-team">
        
        <!-- Enable UUID extension -->
        <sql>CREATE EXTENSION IF NOT EXISTS "uuid-ossp";</sql>
        
        <!-- Create Business Unit table -->
        <createTable tableName="business_unit">
            <column name="id" type="UUID" defaultValueComputed="uuid_generate_v4()">
                <constraints primaryKey="true" nullable="false"/>
            </column>
            <column name="name" type="VARCHAR(255)">
                <constraints nullable="false" unique="true"/>
            </column>
            <column name="created_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP">
                <constraints nullable="false"/>
            </column>
            <column name="updated_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP">
                <constraints nullable="false"/>
            </column>
        </createTable>

        <!-- Create Environment table -->
        <createTable tableName="environment">
            <column name="id" type="UUID" defaultValueComputed="uuid_generate_v4()">
                <constraints primaryKey="true" nullable="false"/>
            </column>
            <column name="name" type="VARCHAR(100)">
                <constraints nullable="false" unique="true"/>
            </column>
            <column name="description" type="TEXT"/>
            <column name="is_production" type="BOOLEAN" defaultValueBoolean="false">
                <constraints nullable="false"/>
            </column>
            <column name="criticality_level" type="INTEGER"/>
            <column name="created_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP">
                <constraints nullable="false"/>
            </column>
            <column name="updated_at" type="TIMESTAMP" defaultValueComputed="CURRENT_TIMESTAMP">
                <constraints nullable="false"/>
            </column>
        </createTable>

        <!-- Indexes -->
        <createIndex tableName="environment" indexName="idx_env_is_production">
            <column name="is_production"/>
        </createIndex>
        
    </changeSet>
    
</databaseChangeLog>

Security Configuration

JWT Token Provider Example

@Component
public class JwtTokenProvider {

    @Value("${jwt.secret}")
    private String jwtSecret;

    @Value("${jwt.expiration}")
    private long jwtExpiration;

    public String generateToken(Authentication authentication) {
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + jwtExpiration);

        return Jwts.builder()
                .setSubject(userDetails.getUsername())
                .setIssuedAt(now)
                .setExpiration(expiryDate)
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }

    public String getUsernameFromToken(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(jwtSecret)
                .parseClaimsJws(token)
                .getBody();
        return claims.getSubject();
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
            return true;
        } catch (SignatureException | MalformedJwtException | ExpiredJwtException | 
                 UnsupportedJwtException | IllegalArgumentException ex) {
            return false;
        }
    }
}

Security Configuration Example

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .cors(cors -> cors.configurationSource(corsConfigurationSource()))
            .sessionManagement(session -> 
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**").permitAll()
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
                .anyRequest().authenticated()
            )
            .addFilterBefore(jwtAuthenticationFilter(), 
                UsernamePasswordAuthenticationFilter.class);
        
        return http.build();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowCredentials(true);
        
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

API Documentation (OpenAPI/Swagger)

Configuration

@Configuration
public class OpenApiConfig {

    @Bean
    public OpenAPI ldpV2OpenAPI() {
        return new OpenAPI()
                .info(new Info()
                        .title("LDPv2 API")
                        .description("Lifecycle Data Platform v2 - Application Management API")
                        .version("1.0.0")
                        .contact(new Contact()
                                .name("LDPv2 Team")
                                .email("team@ldpv2.com"))
                        .license(new License()
                                .name("Proprietary")
                                .url("https://ldpv2.com/license")))
                .addSecurityItem(new SecurityRequirement().addList("bearerAuth"))
                .components(new Components()
                        .addSecuritySchemes("bearerAuth",
                                new SecurityScheme()
                                        .type(SecurityScheme.Type.HTTP)
                                        .scheme("bearer")
                                        .bearerFormat("JWT")));
    }
}

Access Swagger UI at: http://localhost:8080/swagger-ui/index.html

Development Workflow

Backend Development Cycle

  1. Create Liquibase migration
  2. Run migration: mvn liquibase:update
  3. Generate JPA entities
  4. Create repository interfaces
  5. Implement service layer
  6. Create DTOs
  7. Implement controller
  8. Write tests
  9. Document API with Swagger annotations
  10. Commit and push

Frontend Development Cycle

  1. Create TypeScript models
  2. Implement Angular service
  3. Create components (list, detail, form)
  4. Add routing
  5. Style with CSS/SCSS
  6. Write tests
  7. Commit and push

Testing Strategy

Backend Testing

// Unit Test Example
@ExtendWith(MockitoExtension.class)
class ApplicationServiceTest {
    
    @Mock
    private ApplicationRepository applicationRepository;
    
    @InjectMocks
    private ApplicationService applicationService;
    
    @Test
    void shouldCreateApplication() {
        // Test implementation
    }
}

// Integration Test Example with Testcontainers
@SpringBootTest
@Testcontainers
class ApplicationIntegrationTest {
    
    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine");
    
    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
    }
    
    @Test
    void shouldSaveAndRetrieveApplication() {
        // Test implementation
    }
}

Frontend Testing

// Component Unit Test
describe('ApplicationListComponent', () => {
  let component: ApplicationListComponent;
  let fixture: ComponentFixture<ApplicationListComponent>;
  let mockApplicationService: jasmine.SpyObj<ApplicationService>;

  beforeEach(async () => {
    const spy = jasmine.createSpyObj('ApplicationService', ['getApplications']);
    
    await TestBed.configureTestingModule({
      imports: [ApplicationListComponent],
      providers: [
        { provide: ApplicationService, useValue: spy }
      ]
    }).compileComponents();

    mockApplicationService = TestBed.inject(ApplicationService) as jasmine.SpyObj<ApplicationService>;
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

Running the Application

Development Mode

Backend

# Using Maven
mvn spring-boot:run

# Using Docker Compose
docker-compose up

Frontend

# Development server
ng serve

# With proxy
ng serve --proxy-config proxy.conf.json

# Access at http://localhost:4200

Production Build

Backend

mvn clean package
java -jar target/ldpv2-backend-1.0.0.jar

Frontend

ng build --configuration production
# Output in dist/ directory

Next Steps

After completing this technical setup:

  1. Proceed to Story 0 (Foundation) to build the Walking Skeleton
  2. Follow the story sequence for iterative development
  3. Maintain test coverage throughout
  4. Document APIs as you build
  5. Commit frequently with meaningful messages

Document Version: 1.0
Last Updated: February 2026
Status: Ready for Development