init: 클린 기반 auth 서버 설계

This commit is contained in:
DongHyeonka
2026-07-24 14:30:18 +09:00
parent 471db0203d
commit 8a1ac1e769
3642 changed files with 275893 additions and 1 deletions
View File
+43
View File
@@ -0,0 +1,43 @@
# domain AGENTS
Role:
- express domain meaning and invariants only
- own entity, value object, domain policy, domain exception
Allowed:
- entity
- value object
- domain service
- invariant check
- domain exception
Forbidden:
- Spring/JPA/Servlet/HTTP/Jackson
- ResponseEntity/status code/API response shape
- repository/client implementations
- MDC/log context
- string-formatted time generation
Rules:
- prefer expressive types over raw strings
- invariants must be enforced at creation/state-change boundary
- domain exception expresses domain meaning only
- domain must run without web/persistence framework
- keep time as type, not formatted string
Read first:
- `/docs/architecture/README.md`
- `/docs/standards/design/value-object.md`
- `/docs/standards/design/dto-domain-entity-separation.md`
- `/docs/standards/language/null.md`
- `/docs/standards/language/time.md`
- `/docs/standards/language/collections-immutability.md`
- `/docs/standards/language/exceptions.md`
- `/docs/standards/language/javadoc.md`
Examples:
- `/docs/examples/design/value-object.md`
- `/docs/examples/language/null.md`
- `/docs/examples/language/time.md`
- `/docs/examples/language/collections-immutability.md`
- `/docs/examples/language/exceptions.md`
+2
View File
@@ -0,0 +1,2 @@
dependencies {
}
Binary file not shown.
+2
View File
@@ -0,0 +1,2 @@
Manifest-Version: 1.0
@@ -0,0 +1,12 @@
package com.project.auth.domain.user.exception;
public abstract class DomainException extends RuntimeException {
protected DomainException(String message) {
super(message);
}
protected DomainException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,8 @@
package com.project.auth.domain.user.exception;
public class InvalidUserEmailException extends DomainException {
public InvalidUserEmailException() {
super("유효한 이메일 형식이 아닙니다.");
}
}
@@ -0,0 +1,8 @@
package com.project.auth.domain.user.exception;
public class InvalidUserNameException extends DomainException {
public InvalidUserNameException() {
super("이름은 2자 이상 20자 이하여야 합니다.");
}
}
@@ -0,0 +1,5 @@
package com.project.auth.domain.user.model;
public enum AuthProvider {
KEYCLOAK
}
@@ -0,0 +1,15 @@
package com.project.auth.domain.user.model;
public record ProviderSubject(String value) {
public ProviderSubject {
if (value == null || value.isBlank()) {
throw new IllegalArgumentException("providerSubject must not be blank.");
}
value = value.trim();
}
public static ProviderSubject from(String value) {
return new ProviderSubject(value);
}
}
@@ -0,0 +1,76 @@
package com.project.auth.domain.user.model;
import java.time.Instant;
import java.util.Objects;
import java.util.UUID;
public final class User {
private final UUID id;
private final UserEmail email;
private final UserName name;
private final AuthProvider provider;
private final ProviderSubject providerSubject;
private final Instant createdAt;
private User(
UUID id,
UserEmail email,
UserName name,
AuthProvider provider,
ProviderSubject providerSubject,
Instant createdAt
) {
this.id = Objects.requireNonNull(id, "id must not be null");
this.email = Objects.requireNonNull(email, "email must not be null");
this.name = Objects.requireNonNull(name, "name must not be null");
this.provider = Objects.requireNonNull(provider, "provider must not be null");
this.providerSubject = Objects.requireNonNull(providerSubject, "providerSubject must not be null");
this.createdAt = Objects.requireNonNull(createdAt, "createdAt must not be null");
}
public static User registerKeycloak(
UUID id,
UserEmail email,
UserName name,
String providerSubject,
Instant createdAt
) {
return new User(id, email, name, AuthProvider.KEYCLOAK, ProviderSubject.from(providerSubject), createdAt);
}
public static User restore(
UUID id,
UserEmail email,
UserName name,
AuthProvider provider,
String providerSubject,
Instant createdAt
) {
return new User(id, email, name, provider, ProviderSubject.from(providerSubject), createdAt);
}
public UUID getId() {
return id;
}
public String getEmail() {
return email.value();
}
public String getName() {
return name.value();
}
public AuthProvider getProvider() {
return provider;
}
public String getProviderSubject() {
return providerSubject.value();
}
public Instant getCreatedAt() {
return createdAt;
}
}
@@ -0,0 +1,33 @@
package com.project.auth.domain.user.model;
import com.project.auth.domain.user.exception.InvalidUserEmailException;
import java.util.Locale;
import java.util.regex.Pattern;
public record UserEmail(String value) {
private static final Pattern EMAIL_PATTERN = Pattern.compile(
"^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$",
Pattern.CASE_INSENSITIVE
);
public UserEmail {
if (value == null || value.isBlank()) {
throw new InvalidUserEmailException();
}
}
public static UserEmail from(String rawEmail) {
if (rawEmail == null || rawEmail.isBlank()) {
throw new InvalidUserEmailException();
}
String normalizedEmail = rawEmail.trim().toLowerCase(Locale.ROOT);
if (!EMAIL_PATTERN.matcher(normalizedEmail).matches()) {
throw new InvalidUserEmailException();
}
return new UserEmail(normalizedEmail);
}
}
@@ -0,0 +1,23 @@
package com.project.auth.domain.user.model;
import com.project.auth.domain.user.exception.InvalidUserNameException;
public record UserName(String value) {
public UserName {
if (value == null || value.isBlank()) {
throw new InvalidUserNameException();
}
String normalizedName = value.trim();
if (normalizedName.length() < 2 || normalizedName.length() > 20) {
throw new InvalidUserNameException();
}
value = normalizedName;
}
public static UserName from(String rawName) {
return new UserName(rawName);
}
}