@startuml onion-architecture skinparam packageStyle rectangle skinparam shadowing false skinparam defaultFontName Arial skinparam defaultFontSize 11 skinparam classAttributeIconSize 0 skinparam ArrowColor #444444 skinparam ArrowThickness 1.2 skinparam package { BackgroundColor<> #FFF9C4 BorderColor<> #F9A825 FontColor<> #5D4037 FontStyle<> bold } skinparam package { BackgroundColor<> #C8E6C9 BorderColor<> #388E3C FontColor<> #1B5E20 FontStyle<> bold } skinparam package { BackgroundColor<> #BBDEFB BorderColor<> #1976D2 FontColor<> #0D47A1 FontStyle<> bold } ' ───────────────────────────────────────────────────────────────────────────── ' DOMAIN LAYER (innermost) ' ───────────────────────────────────────────────────────────────────────────── package "Domain Layer" <> { package "model" { class Person { - id : Long - firstName : String - lastName : String - age : int - phoneNumber : String - email : String - category : Category + getId() : Long + getFirstName() : String + getLastName() : String + getAge() : int + getPhoneNumber() : String + getEmail() : String + getCategory() : Category } class Category { - id : Long - type : String + getId() : Long + getType() : String } } package "repository" { interface PersonRepository { + findById(id : Long) : Optional + findByFirstName(firstName : String) : Optional + findByLastName(lastName : String) : Optional + findAll() : Optional> + save(person : Person) : Person + deleteById(id : Long) : boolean } } package "exception" { class DomainException { + DomainException(message : String) } } } ' ───────────────────────────────────────────────────────────────────────────── ' APPLICATION LAYER ' ───────────────────────────────────────────────────────────────────────────── package "Application Layer" <> { package "usecase" { class SavePersonUseCase { - repository : PersonRepository + SavePersonUseCase(repository : PersonRepository) + execute(command : SavePersonCommand) : PersonResponse } class GetPersonUseCase { - repository : PersonRepository + GetPersonUseCase(repository : PersonRepository) + execute(id : Long) : PersonResponse + executeAll() : List } } package "dto" { class SavePersonCommand <> { firstName : String lastName : String age : int phoneNumber : String email : String address : String categoryId : Long categoryType : String } class PersonResponse <> { id : Long firstName : String lastName : String age : int phoneNumber : String email : String categoryId : Long categoryType : String } } } ' ───────────────────────────────────────────────────────────────────────────── ' INFRASTRUCTURE LAYER (outermost) ' ───────────────────────────────────────────────────────────────────────────── package "Infrastructure Layer" <> { package "web" { class PersonController { - savePersonUseCase : SavePersonUseCase - getPersonUseCase : GetPersonUseCase + getPerson(id : Long) : ResponseEntity + getAllPersons() : ResponseEntity> + savePerson(command : SavePersonCommand) : ResponseEntity } } package "persistence" { class PersonRepositoryAdapter { - repository : SpringDataPersonRepository + findById(id : Long) : Optional + findByFirstName(firstName : String) : Optional + findByLastName(lastName : String) : Optional + findAll() : Optional> + save(person : Person) : Person + deleteById(id : Long) : boolean } interface SpringDataPersonRepository { + findByFirstName(firstName : String) : Optional + findByLastName(lastName : String) : Optional } class JpaPersonEntity { - id : Long - firstName : String - lastName : String - age : int - phoneNumber : String - email : String - category : JpaCategoryEntity } class JpaCategoryEntity { - id : Long - type : String } } package "config" { class ApplicationConfig <<@Configuration>> { + savePersonUseCase(repo : PersonRepository) : SavePersonUseCase + getPersonUseCase(repo : PersonRepository) : GetPersonUseCase } class Application <<@SpringBootApplication>> { + main(args : String[]) } } } ' ───────────────────────────────────────────────────────────────────────────── ' DOMAIN: internal relationships ' ───────────────────────────────────────────────────────────────────────────── Person "1" *-- "1" Category : has Person ..> DomainException : <> Category ..> DomainException : <> ' ───────────────────────────────────────────────────────────────────────────── ' APPLICATION → DOMAIN ' ───────────────────────────────────────────────────────────────────────────── SavePersonUseCase --> PersonRepository : uses SavePersonUseCase ..> Person : creates SavePersonUseCase ..> Category : creates SavePersonUseCase ..> PersonResponse : returns SavePersonUseCase ..> SavePersonCommand : receives GetPersonUseCase --> PersonRepository : uses GetPersonUseCase ..> PersonResponse : returns ' ───────────────────────────────────────────────────────────────────────────── ' INFRASTRUCTURE → APPLICATION / DOMAIN ' ───────────────────────────────────────────────────────────────────────────── PersonController --> SavePersonUseCase : delegates to PersonController --> GetPersonUseCase : delegates to PersonController ..> DomainException : catches PersonRepositoryAdapter ..|> PersonRepository : implements PersonRepositoryAdapter --> SpringDataPersonRepository : uses PersonRepositoryAdapter ..> JpaPersonEntity : maps PersonRepositoryAdapter ..> JpaCategoryEntity : maps PersonRepositoryAdapter ..> Person : maps to / from PersonRepositoryAdapter ..> Category : maps to / from SpringDataPersonRepository --> JpaPersonEntity : manages JpaPersonEntity "1" *-- "1" JpaCategoryEntity : contains ApplicationConfig ..> SavePersonUseCase : <<@Bean>> ApplicationConfig ..> GetPersonUseCase : <<@Bean>> ApplicationConfig --> PersonRepository : injects legend right Layer dependency rule Infrastructure → Application → Domain Outer layers depend on inner layers. Domain has NO external dependencies. ---- ■ Domain Layer ■ Application Layer ■ Infrastructure Layer endlegend @enduml