commit 392516f95c2d32afbb2d8f0e1ad1d0e8b0bf3884 Author: ShopForge Course Date: Thu Sep 17 12:17:07 2026 +0000 P02: Maven multi-module skeleton — domain, application, adapter-persistence, adapter-web, bootstrap diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..e89a5dd --- /dev/null +++ b/.gitea/workflows/build.yml @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b2e433c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +target/ +*.class +.idea/ +*.iml diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..2bb0ec9 --- /dev/null +++ b/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.3.4 + + + + dev.shopforge + shopforge + 0.1.0-SNAPSHOT + pom + ShopForge + + + 21 + UTF-8 + + + + shopforge-domain + shopforge-application + shopforge-adapter-persistence + shopforge-adapter-web + shopforge-bootstrap + + + + + + dev.shopforgeshopforge-domain${project.version} + + + dev.shopforgeshopforge-application${project.version} + + + dev.shopforgeshopforge-adapter-persistence${project.version} + + + dev.shopforgeshopforge-adapter-web${project.version} + + + + diff --git a/shopforge-adapter-persistence/pom.xml b/shopforge-adapter-persistence/pom.xml new file mode 100644 index 0000000..3ad4313 --- /dev/null +++ b/shopforge-adapter-persistence/pom.xml @@ -0,0 +1,14 @@ + + + 4.0.0 + dev.shopforgeshopforge0.1.0-SNAPSHOT + shopforge-adapter-persistence + ShopForge :: adapter-persistence + + + dev.shopforgeshopforge-domain + org.springframework.bootspring-boot-starter-data-jpa + + diff --git a/shopforge-adapter-persistence/src/main/java/dev/shopforge/persistence/JpaShopRepository.java b/shopforge-adapter-persistence/src/main/java/dev/shopforge/persistence/JpaShopRepository.java new file mode 100644 index 0000000..c6b95fa --- /dev/null +++ b/shopforge-adapter-persistence/src/main/java/dev/shopforge/persistence/JpaShopRepository.java @@ -0,0 +1,8 @@ +package dev.shopforge.persistence; + +import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + +interface JpaShopRepository extends JpaRepository { + Optional findBySubdomain(String subdomain); +} diff --git a/shopforge-adapter-persistence/src/main/java/dev/shopforge/persistence/ShopEntity.java b/shopforge-adapter-persistence/src/main/java/dev/shopforge/persistence/ShopEntity.java new file mode 100644 index 0000000..871fe68 --- /dev/null +++ b/shopforge-adapter-persistence/src/main/java/dev/shopforge/persistence/ShopEntity.java @@ -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; } +} diff --git a/shopforge-adapter-persistence/src/main/java/dev/shopforge/persistence/ShopRepositoryAdapter.java b/shopforge-adapter-persistence/src/main/java/dev/shopforge/persistence/ShopRepositoryAdapter.java new file mode 100644 index 0000000..f2c75a1 --- /dev/null +++ b/shopforge-adapter-persistence/src/main/java/dev/shopforge/persistence/ShopRepositoryAdapter.java @@ -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 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()); + } +} diff --git a/shopforge-adapter-web/pom.xml b/shopforge-adapter-web/pom.xml new file mode 100644 index 0000000..6067491 --- /dev/null +++ b/shopforge-adapter-web/pom.xml @@ -0,0 +1,14 @@ + + + 4.0.0 + dev.shopforgeshopforge0.1.0-SNAPSHOT + shopforge-adapter-web + ShopForge :: adapter-web + + + dev.shopforgeshopforge-application + org.springframework.bootspring-boot-starter-web + + diff --git a/shopforge-adapter-web/src/main/java/dev/shopforge/web/HealthController.java b/shopforge-adapter-web/src/main/java/dev/shopforge/web/HealthController.java new file mode 100644 index 0000000..65cbdc9 --- /dev/null +++ b/shopforge-adapter-web/src/main/java/dev/shopforge/web/HealthController.java @@ -0,0 +1,13 @@ +package dev.shopforge.web; + +import org.springframework.web.bind.annotation.*; +import java.util.Map; + +@RestController +class HealthController { + + @GetMapping("/hello") + Map hello() { + return Map.of("service", "shopforge", "status", "up"); + } +} diff --git a/shopforge-adapter-web/src/main/java/dev/shopforge/web/ShopController.java b/shopforge-adapter-web/src/main/java/dev/shopforge/web/ShopController.java new file mode 100644 index 0000000..964ad83 --- /dev/null +++ b/shopforge-adapter-web/src/main/java/dev/shopforge/web/ShopController.java @@ -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()); + } +} diff --git a/shopforge-adapter-web/src/main/java/dev/shopforge/web/ShopResponse.java b/shopforge-adapter-web/src/main/java/dev/shopforge/web/ShopResponse.java new file mode 100644 index 0000000..e6dfe68 --- /dev/null +++ b/shopforge-adapter-web/src/main/java/dev/shopforge/web/ShopResponse.java @@ -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) { } diff --git a/shopforge-application/pom.xml b/shopforge-application/pom.xml new file mode 100644 index 0000000..f95dbeb --- /dev/null +++ b/shopforge-application/pom.xml @@ -0,0 +1,15 @@ + + + 4.0.0 + dev.shopforgeshopforge0.1.0-SNAPSHOT + shopforge-application + ShopForge :: application + + + dev.shopforgeshopforge-domain + + org.springframeworkspring-context + + diff --git a/shopforge-application/src/main/java/dev/shopforge/application/ShopNotFound.java b/shopforge-application/src/main/java/dev/shopforge/application/ShopNotFound.java new file mode 100644 index 0000000..75c3168 --- /dev/null +++ b/shopforge-application/src/main/java/dev/shopforge/application/ShopNotFound.java @@ -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); + } +} diff --git a/shopforge-application/src/main/java/dev/shopforge/application/ShopService.java b/shopforge-application/src/main/java/dev/shopforge/application/ShopService.java new file mode 100644 index 0000000..52e99b0 --- /dev/null +++ b/shopforge-application/src/main/java/dev/shopforge/application/ShopService.java @@ -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)); + } +} diff --git a/shopforge-bootstrap/pom.xml b/shopforge-bootstrap/pom.xml new file mode 100644 index 0000000..f75017f --- /dev/null +++ b/shopforge-bootstrap/pom.xml @@ -0,0 +1,23 @@ + + + 4.0.0 + dev.shopforgeshopforge0.1.0-SNAPSHOT + shopforge-bootstrap + ShopForge :: bootstrap + + + dev.shopforgeshopforge-adapter-web + dev.shopforgeshopforge-adapter-persistence + com.h2databaseh2runtime + org.springframework.bootspring-boot-starter-testtest + + + + + + org.springframework.bootspring-boot-maven-plugin + + + diff --git a/shopforge-bootstrap/src/main/java/dev/shopforge/ShopForgeApplication.java b/shopforge-bootstrap/src/main/java/dev/shopforge/ShopForgeApplication.java new file mode 100644 index 0000000..8cca09b --- /dev/null +++ b/shopforge-bootstrap/src/main/java/dev/shopforge/ShopForgeApplication.java @@ -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); + } +} diff --git a/shopforge-bootstrap/src/main/resources/application.properties b/shopforge-bootstrap/src/main/resources/application.properties new file mode 100644 index 0000000..6537d24 --- /dev/null +++ b/shopforge-bootstrap/src/main/resources/application.properties @@ -0,0 +1,3 @@ +spring.datasource.url=jdbc:h2:mem:shopforge +spring.jpa.hibernate.ddl-auto=update +server.port=19080 diff --git a/shopforge-bootstrap/src/test/java/dev/shopforge/ContextLoadTest.java b/shopforge-bootstrap/src/test/java/dev/shopforge/ContextLoadTest.java new file mode 100644 index 0000000..1daa765 --- /dev/null +++ b/shopforge-bootstrap/src/test/java/dev/shopforge/ContextLoadTest.java @@ -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() { } +} diff --git a/shopforge-domain/pom.xml b/shopforge-domain/pom.xml new file mode 100644 index 0000000..ee65151 --- /dev/null +++ b/shopforge-domain/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + dev.shopforgeshopforge0.1.0-SNAPSHOT + + shopforge-domain + ShopForge :: domain + + + + + org.junit.jupiter + junit-jupiter + test + + + diff --git a/shopforge-domain/src/main/java/dev/shopforge/domain/Shop.java b/shopforge-domain/src/main/java/dev/shopforge/domain/Shop.java new file mode 100644 index 0000000..7aafd3b --- /dev/null +++ b/shopforge-domain/src/main/java/dev/shopforge/domain/Shop.java @@ -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); } +} diff --git a/shopforge-domain/src/main/java/dev/shopforge/domain/ShopRepository.java b/shopforge-domain/src/main/java/dev/shopforge/domain/ShopRepository.java new file mode 100644 index 0000000..f298a86 --- /dev/null +++ b/shopforge-domain/src/main/java/dev/shopforge/domain/ShopRepository.java @@ -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 bySubdomain(String subdomain); + Shop save(Shop shop); +} diff --git a/shopforge-domain/src/main/java/dev/shopforge/domain/TenantId.java b/shopforge-domain/src/main/java/dev/shopforge/domain/TenantId.java new file mode 100644 index 0000000..6378495 --- /dev/null +++ b/shopforge-domain/src/main/java/dev/shopforge/domain/TenantId.java @@ -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"); + } +} diff --git a/shopforge-domain/src/test/java/dev/shopforge/domain/ShopTest.java b/shopforge-domain/src/test/java/dev/shopforge/domain/ShopTest.java new file mode 100644 index 0000000..bedce8a --- /dev/null +++ b/shopforge-domain/src/test/java/dev/shopforge/domain/ShopTest.java @@ -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()); + } +}