Skip to content

Instantly share code, notes, and snippets.

@soudmaijer
Last active November 6, 2024 14:00
Show Gist options
  • Save soudmaijer/13e8c935bc59bc37b4f70c1fbd36da72 to your computer and use it in GitHub Desktop.
Save soudmaijer/13e8c935bc59bc37b4f70c1fbd36da72 to your computer and use it in GitHub Desktop.
Separation of different application responsibilities
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