Last active
November 6, 2024 14:00
-
-
Save soudmaijer/13e8c935bc59bc37b4f70c1fbd36da72 to your computer and use it in GitHub Desktop.
Separation of different application responsibilities
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import jakarta.validation.Valid | |
import org.apache.logging.log4j.LogManager | |
import org.springframework.data.annotation.Id | |
import org.springframework.data.relational.core.mapping.Column | |
import org.springframework.data.relational.core.mapping.Table | |
import org.springframework.data.repository.CrudRepository | |
import org.springframework.http.ResponseEntity | |
import org.springframework.stereotype.Repository | |
import org.springframework.stereotype.Service | |
import org.springframework.transaction.annotation.Transactional | |
import org.springframework.web.bind.annotation.PostMapping | |
import org.springframework.web.bind.annotation.RequestBody | |
import org.springframework.web.bind.annotation.RestController | |
import java.net.URI | |
import java.time.LocalDateTime | |
package api | |
// generated in api model package, classes have no logic! | |
class Address(val postalCode: String, val houseNumber: String) | |
class User(val name: String, val age: Int, val address: Address, val id: String? = null) | |
// generated api interface, needs to be implemented by the application | |
interface UserApi { | |
@PostMapping("/users") | |
fun createUser(@Valid @RequestBody user: api.User): ResponseEntity<Void> | |
} | |
// controller implementation of the generated api interface | |
@RestController | |
class UserController(private val userService: UserService): UserApi { | |
override fun createUser(user: api.User): ResponseEntity<Void> { | |
val newUser = userService.createUser(domain.User(name = user.name, age = user.age, address = domain.Address(postalCode = user.address.postalCode, houseNumber = user.address.houseNumber))) | |
return ResponseEntity.created(URI.create("/users/${newUser.id}")).build() | |
} | |
} | |
package domain | |
// domain objects | |
class Address(val postalCode: String, val houseNumber: String) | |
class User(val name: String, val age: Int, val address: Address? = null, val id: Long? = null) { | |
// domain objects can (should?) include domain logic, for example: | |
fun isAdult() = age > 17 | |
fun isRetired() = age > 67 | |
} | |
// This service implements all the user related application logic | |
@Service | |
class UserService( | |
private val userRepository: UserRepository, | |
private val eventPublisher: EventPublisher | |
) { | |
private val log = LogManager.getLogger() | |
/** | |
* This function implements everything that is needed for creating a user. | |
*/ | |
@Transactional | |
fun createUser(user: domain.User): domain.User { | |
// We might want to verify some (business) rules before creating the user, for example: | |
val existingUser = userRepository.findByName(user.name) | |
if(existingUser != null) { | |
throw UserAlreadyExistsException("User with name: ${user.name} already exists!") | |
} | |
// Store user in datastore? | |
val newUser = userRepository.save(repository.User(name = user.name, age = user.age)) | |
log.info("New user created! $newUser") | |
// Maybe publish some event to a message queue here... | |
eventPublisher.publishUserCreatedEvent(event.UserCreatedEvent( /* etc */ )) | |
// And when everything is successfully done, return the newly created user | |
return domain.User(name = newUser.name, age = newUser.age, id = newUser.id) | |
} | |
} | |
class UserAlreadyExistsException(msg: String) : RuntimeException(msg) | |
package repository | |
// All datastore related logic, like mapping to a table, etc. | |
@Table("users") | |
class User( | |
@Id val id: Long? = null, | |
@Column("name") | |
val name: String, | |
@Column("age") | |
val age: Int, | |
@Column("creation_date_time") | |
val creationDateTime: LocalDateTime = LocalDateTime.now() | |
/* etc */ | |
) | |
@Repository | |
interface UserRepository : CrudRepository<repository.User, Long> { | |
fun findByName(name: String): repository.User? | |
} | |
package event | |
// event publishing related logic, again a different concern. | |
class UserCreatedEvent(/* properties like name? id? creation date, etc. */) | |
@Service | |
class EventPublisher { | |
fun publishUserCreatedEvent(event: UserCreatedEvent) { | |
/* etc */ | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment