P02: Maven multi-module skeleton — domain, application, adapter-persistence, adapter-web, bootstrap
build / build (push) Failing after 18s

This commit is contained in:
ShopForge Course
2026-09-17 12:17:07 +00:00
commit 392516f95c
23 changed files with 363 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
name: build
on: [push]
jobs:
build:
runs-on: ubuntu-latest
# Runner ke paas sirf do label hain (ubuntu-latest, node-22) — Java ka koi nahi.
# Isliye job ko Maven ke apne image ke andar chalate hain. Build ka environment
# pinned ho jaata hai: jo yahan chala, wahi laptop pe chalega.
container:
image: maven:3.9-eclipse-temurin-21
steps:
- uses: actions/checkout@v4
- name: Build aur test
run: mvn -B clean verify
+4
View File
@@ -0,0 +1,4 @@
target/
*.class
.idea/
*.iml
+49
View File
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version>
<relativePath/>
</parent>
<groupId>dev.shopforge</groupId>
<artifactId>shopforge</artifactId>
<version>0.1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>ShopForge</name>
<properties>
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<modules>
<module>shopforge-domain</module>
<module>shopforge-application</module>
<module>shopforge-adapter-persistence</module>
<module>shopforge-adapter-web</module>
<module>shopforge-bootstrap</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>dev.shopforge</groupId><artifactId>shopforge-domain</artifactId><version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.shopforge</groupId><artifactId>shopforge-application</artifactId><version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.shopforge</groupId><artifactId>shopforge-adapter-persistence</artifactId><version>${project.version}</version>
</dependency>
<dependency>
<groupId>dev.shopforge</groupId><artifactId>shopforge-adapter-web</artifactId><version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent><groupId>dev.shopforge</groupId><artifactId>shopforge</artifactId><version>0.1.0-SNAPSHOT</version></parent>
<artifactId>shopforge-adapter-persistence</artifactId>
<name>ShopForge :: adapter-persistence</name>
<dependencies>
<dependency><groupId>dev.shopforge</groupId><artifactId>shopforge-domain</artifactId></dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency>
</dependencies>
</project>
@@ -0,0 +1,8 @@
package dev.shopforge.persistence;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
interface JpaShopRepository extends JpaRepository<ShopEntity, String> {
Optional<ShopEntity> findBySubdomain(String subdomain);
}
@@ -0,0 +1,28 @@
package dev.shopforge.persistence;
import jakarta.persistence.*;
/** DB ka naksha. Ye domain ka Shop NAHI hai — jaanbujhkar alag rakha gaya hai. */
@Entity
@Table(name = "shop")
public class ShopEntity {
@Id
private String id;
private String naam;
@Column(unique = true)
private String subdomain;
private boolean active;
protected ShopEntity() { }
public ShopEntity(String id, String naam, String subdomain, boolean active) {
this.id = id; this.naam = naam; this.subdomain = subdomain; this.active = active;
}
public String getId() { return id; }
public String getNaam() { return naam; }
public String getSubdomain() { return subdomain; }
public boolean isActive() { return active; }
}
@@ -0,0 +1,30 @@
package dev.shopforge.persistence;
import dev.shopforge.domain.*;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/** ADAPTER — domain ka port yahan poora hota hai. Anuvaad yahin hota hai, kahin aur nahi. */
@Repository
public class ShopRepositoryAdapter implements ShopRepository {
private final JpaShopRepository jpa;
ShopRepositoryAdapter(JpaShopRepository jpa) { this.jpa = jpa; }
@Override
public Optional<Shop> bySubdomain(String subdomain) {
return jpa.findBySubdomain(subdomain).map(this::toDomain);
}
@Override
public Shop save(Shop shop) {
ShopEntity saved = jpa.save(new ShopEntity(
shop.id().value(), shop.naam(), shop.subdomain(), shop.active()));
return toDomain(saved);
}
private Shop toDomain(ShopEntity e) {
return new Shop(new TenantId(e.getId()), e.getNaam(), e.getSubdomain(), e.isActive());
}
}
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent><groupId>dev.shopforge</groupId><artifactId>shopforge</artifactId><version>0.1.0-SNAPSHOT</version></parent>
<artifactId>shopforge-adapter-web</artifactId>
<name>ShopForge :: adapter-web</name>
<dependencies>
<dependency><groupId>dev.shopforge</groupId><artifactId>shopforge-application</artifactId></dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
</dependencies>
</project>
@@ -0,0 +1,13 @@
package dev.shopforge.web;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
class HealthController {
@GetMapping("/hello")
Map<String, String> hello() {
return Map.of("service", "shopforge", "status", "up");
}
}
@@ -0,0 +1,20 @@
package dev.shopforge.web;
import dev.shopforge.application.ShopService;
import dev.shopforge.domain.Shop;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/shops")
class ShopController {
private final ShopService service;
ShopController(ShopService service) { this.service = service; }
@GetMapping("/{subdomain}")
ShopResponse get(@PathVariable String subdomain) {
Shop s = service.bySubdomain(subdomain);
return new ShopResponse(s.id().value(), s.naam(), s.subdomain(), s.active());
}
}
@@ -0,0 +1,4 @@
package dev.shopforge.web;
/** DTO — domain ka Shop bahar nahi jaata (Java Doc 23 ka sabak). */
record ShopResponse(String id, String naam, String subdomain, boolean active) { }
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent><groupId>dev.shopforge</groupId><artifactId>shopforge</artifactId><version>0.1.0-SNAPSHOT</version></parent>
<artifactId>shopforge-application</artifactId>
<name>ShopForge :: application</name>
<dependencies>
<dependency><groupId>dev.shopforge</groupId><artifactId>shopforge-domain</artifactId></dependency>
<!-- sirf spring-context — DI ke liye. Web nahi, JPA nahi. -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId></dependency>
</dependencies>
</project>
@@ -0,0 +1,7 @@
package dev.shopforge.application;
public class ShopNotFound extends RuntimeException {
public ShopNotFound(String subdomain) {
super("Is subdomain ki koi shop nahi mili: " + subdomain);
}
}
@@ -0,0 +1,21 @@
package dev.shopforge.application;
import dev.shopforge.domain.Shop;
import dev.shopforge.domain.ShopRepository;
import org.springframework.stereotype.Service;
/** Use case. Domain ka port maangta hai — JPA ka naam tak nahi jaanta. */
@Service
public class ShopService {
private final ShopRepository shops;
public ShopService(ShopRepository shops) { // constructor injection — Java Doc 22
this.shops = shops;
}
public Shop bySubdomain(String subdomain) {
return shops.bySubdomain(subdomain)
.orElseThrow(() -> new ShopNotFound(subdomain));
}
}
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent><groupId>dev.shopforge</groupId><artifactId>shopforge</artifactId><version>0.1.0-SNAPSHOT</version></parent>
<artifactId>shopforge-bootstrap</artifactId>
<name>ShopForge :: bootstrap</name>
<dependencies>
<dependency><groupId>dev.shopforge</groupId><artifactId>shopforge-adapter-web</artifactId></dependency>
<dependency><groupId>dev.shopforge</groupId><artifactId>shopforge-adapter-persistence</artifactId></dependency>
<dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope></dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
</dependencies>
<build>
<plugins>
<!-- fat jar sirf YAHIN banta hai — baaki module saadi jar hain -->
<plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,15 @@
package dev.shopforge;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@SpringBootApplication(scanBasePackages = "dev.shopforge")
@EntityScan("dev.shopforge.persistence")
@EnableJpaRepositories("dev.shopforge.persistence")
public class ShopForgeApplication {
public static void main(String[] args) {
SpringApplication.run(ShopForgeApplication.class, args);
}
}
@@ -0,0 +1,3 @@
spring.datasource.url=jdbc:h2:mem:shopforge
spring.jpa.hibernate.ddl-auto=update
server.port=19080
@@ -0,0 +1,10 @@
package dev.shopforge;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class ContextLoadTest {
@Test
void context_uthta_hai() { }
}
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>dev.shopforge</groupId><artifactId>shopforge</artifactId><version>0.1.0-SNAPSHOT</version>
</parent>
<artifactId>shopforge-domain</artifactId>
<name>ShopForge :: domain</name>
<!-- 🔴 YAHAN JAANBUJHKAR KUCH NAHI HAI.
Ek bhi Spring dependency nahi. Ye khaalipan is module ka poora matlab hai. -->
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,8 @@
package dev.shopforge.domain;
/** Ek shop. Sirf Java — koi annotation, koi framework nahi. */
public record Shop(TenantId id, String naam, String subdomain, boolean active) {
public Shop activate() { return new Shop(id, naam, subdomain, true); }
public Shop deactivate() { return new Shop(id, naam, subdomain, false); }
}
@@ -0,0 +1,12 @@
package dev.shopforge.domain;
import java.util.Optional;
/**
* PORT — domain batata hai ki use kya chahiye, na ki wo kahan se aayega.
* Iska implementation adapter-persistence me hai. Teer domain ki taraf aata hai, jaata nahi.
*/
public interface ShopRepository {
Optional<Shop> bySubdomain(String subdomain);
Shop save(Shop shop);
}
@@ -0,0 +1,9 @@
package dev.shopforge.domain;
/** Ek shop ki pehchaan. Value object — bani to badal nahi sakti. */
public record TenantId(String value) {
public TenantId {
if (value == null || value.isBlank())
throw new IllegalArgumentException("TenantId khaali nahi ho sakta");
}
}
@@ -0,0 +1,20 @@
package dev.shopforge.domain;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class ShopTest {
@Test
void khaali_tenantId_bante_hi_mana_hai() {
assertThrows(IllegalArgumentException.class, () -> new TenantId(" "));
}
@Test
void activate_naya_object_deta_hai_purana_nahi_badalta() {
Shop band = new Shop(new TenantId("t1"), "Chai Point", "chaipoint", false);
Shop chalu = band.activate();
assertFalse(band.active());
assertTrue(chalu.active());
}
}