init: 클린 기반 auth 서버 설계
This commit is contained in:
@@ -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`
|
||||
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
dependencies {
|
||||
}
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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);
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.project.auth.domain.user.exception;
|
||||
|
||||
public class InvalidUserEmailException extends DomainException {
|
||||
|
||||
public InvalidUserEmailException() {
|
||||
super("유효한 이메일 형식이 아닙니다.");
|
||||
}
|
||||
}
|
||||
+8
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user