# 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 ```bash # 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 ```yaml 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 ```yaml 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 ```bash npm install -g @angular/cli@18 ng new ldpv2-frontend cd ldpv2-frontend ``` #### 2. Install Dependencies ```bash # Angular Material (or PrimeNG) ng add @angular/material # Additional dependencies npm install --save \ @angular/common \ @angular/forms \ rxjs ``` #### 3. Configure Environment ```typescript // 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 ```json // proxy.conf.json { "/api": { "target": "http://localhost:8080", "secure": false, "changeOrigin": true } } ``` Update `angular.json`: ```json "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { "proxyConfig": "proxy.conf.json" } } ``` ## Database Schema Management ### Liquibase Changelog Structure ```xml ``` ### Example Migration File ```xml CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; ``` ## Security Configuration ### JWT Token Provider Example ```java @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 ```java @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 ```java @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 ```java // 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 ```typescript // Component Unit Test describe('ApplicationListComponent', () => { let component: ApplicationListComponent; let fixture: ComponentFixture; let mockApplicationService: jasmine.SpyObj; 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; }); it('should create', () => { expect(component).toBeTruthy(); }); }); ``` ## Running the Application ### Development Mode #### Backend ```bash # Using Maven mvn spring-boot:run # Using Docker Compose docker-compose up ``` #### Frontend ```bash # Development server ng serve # With proxy ng serve --proxy-config proxy.conf.json # Access at http://localhost:4200 ``` ### Production Build #### Backend ```bash mvn clean package java -jar target/ldpv2-backend-1.0.0.jar ``` #### Frontend ```bash 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