ื‘ืขื™ื•ืช ืฉืœ ืขื™ื‘ื•ื“ ืฉืื™ืœืชื•ืช ืืฆื•ื•ื” ื•ื”ืคืชืจื•ื ื•ืช ืฉืœื”ืŸ (ื—ืœืง 1)

ื‘ืขื™ื•ืช ืฉืœ ืขื™ื‘ื•ื“ ืฉืื™ืœืชื•ืช ืืฆื•ื•ื” ื•ื”ืคืชืจื•ื ื•ืช ืฉืœื”ืŸ (ื—ืœืง 1)ื›ืžืขื˜ ื›ืœ ืžื•ืฆืจื™ ื”ืชื•ื›ื ื” ื”ืžื•ื“ืจื ื™ื™ื ืžื•ืจื›ื‘ื™ื ืžืžืกืคืจ ืฉื™ืจื•ืชื™ื. ืœืขืชื™ื ืงืจื•ื‘ื•ืช, ื–ืžื ื™ ืชื’ื•ื‘ื” ืืจื•ื›ื™ื ืฉืœ ืขืจื•ืฆื™ื ื‘ื™ืŸ ืฉื™ืจื•ืชื™ื ื”ื•ืคื›ื™ื ืœืžืงื•ืจ ืœื‘ืขื™ื•ืช ื‘ื™ืฆื•ืขื™ื. ื”ืคืชืจื•ืŸ ื”ืกื˜ื ื“ืจื˜ื™ ืœื‘ืขื™ื” ืžืกื•ื’ ื–ื” ื”ื•ื ืœืืจื•ื– ืžืกืคืจ ื‘ืงืฉื•ืช ื‘ื™ืŸ ืฉื™ืจื•ืชื™ื ืœื—ื‘ื™ืœื” ืื—ืช, ื”ื ืงืจืืช ืืฆื•ื•ื”.

ืื ืืชื” ืžืฉืชืžืฉ ื‘ืขื™ื‘ื•ื“ ืืฆื•ื•ื”, ื™ื™ืชื›ืŸ ืฉืœื ืชื”ื™ื” ืžืจื•ืฆื” ืžื”ืชื•ืฆืื•ืช ืžื‘ื—ื™ื ืช ื‘ื™ืฆื•ืขื™ื ืื• ื‘ื”ื™ืจื•ืช ืงื•ื“. ืฉื™ื˜ื” ื–ื• ืื™ื ื” ืงืœื” ืœืžืชืงืฉืจ ื›ืคื™ ืฉืืชื” ืขืฉื•ื™ ืœื—ืฉื•ื‘. ืœืžื˜ืจื•ืช ืฉื•ื ื•ืช ื•ื‘ืžืฆื‘ื™ื ืฉื•ื ื™ื, ื”ืคืชืจื•ื ื•ืช ื™ื›ื•ืœื™ื ืœื”ืฉืชื ื•ืช ืžืื•ื“. ื‘ืขื–ืจืช ื“ื•ื’ืžืื•ืช ืกืคืฆื™ืคื™ื•ืช, ืืจืื” ืืช ื”ื™ืชืจื•ื ื•ืช ื•ื”ื—ืกืจื•ื ื•ืช ืฉืœ ืžืกืคืจ ื’ื™ืฉื•ืช.

ืคืจื•ื™ืงื˜ ื”ื“ื’ืžื”

ืœืžืขืŸ ื”ื‘ื”ื™ืจื•ืช, ื‘ื•ืื• ื ืกืชื›ืœ ืขืœ ื“ื•ื’ืžื” ืฉืœ ืื—ื“ ืžื”ืฉื™ืจื•ืชื™ื ื‘ืืคืœื™ืงืฆื™ื” ืฉืขืœื™ื” ืื ื™ ืขื•ื‘ื“ ื›ืขืช.

ื”ืกื‘ืจ ืขืœ ื‘ื—ื™ืจืช ืคืœื˜ืคื•ืจืžื” ืœื“ื•ื’ืžืื•ืชื”ื‘ืขื™ื” ืฉืœ ื‘ื™ืฆื•ืขื™ื ื’ืจื•ืขื™ื ื”ื™ื ื“ื™ ื›ืœืœื™ืช ื•ืื™ื ื” ื ื•ื’ืขืช ืœืฉื•ื ืฉืคื•ืช ืื• ืคืœื˜ืคื•ืจืžื•ืช ืกืคืฆื™ืคื™ื•ืช. ืžืืžืจ ื–ื” ื™ืฉืชืžืฉ ื‘ื“ื•ื’ืžืื•ืช ืงื•ื“ ืฉืœ Spring + Kotlin ื›ื“ื™ ืœื”ื“ื’ื™ื ื‘ืขื™ื•ืช ื•ืคืชืจื•ื ื•ืช. ืงื•ื˜ืœื™ืŸ ืžื•ื‘ืŸ (ืื• ืœื ืžื•ื‘ืŸ) ื‘ืื•ืชื” ืžื™ื“ื” ืœืžืคืชื—ื™ Java ื•-C#; ื‘ื ื•ืกืฃ, ื”ืงื•ื“ ืงื•ืžืคืงื˜ื™ ื•ืžื•ื‘ืŸ ื™ื•ืชืจ ืžืืฉืจ ื‘-Java. ื›ื“ื™ ืœื”ืงืœ ืขืœ ื”ื”ื‘ื ื” ืขื‘ื•ืจ ืžืคืชื—ื™ ื’'ืื•ื•ื” ื˜ื”ื•ืจื™ื, ืืžื ืข ืžื”ืงืกื ื”ืฉื—ื•ืจ ืฉืœ ืงื•ื˜ืœื™ืŸ ื•ืืฉืชืžืฉ ืจืง ื‘ืงืกื ื”ืœื‘ืŸ (ื‘ืจื•ื— ืœื•ืžื‘ื•ืง). ื™ื”ื™ื• ื›ืžื” ืฉื™ื˜ื•ืช ื”ืจื—ื‘ื”, ืื‘ืœ ื”ืŸ ืœืžืขืฉื” ืžื•ื›ืจื•ืช ืœื›ืœ ืžืชื›ื ืชื™ ื’'ืื•ื•ื” ื›ืฉื™ื˜ื•ืช ืกื˜ื˜ื™ื•ืช, ื›ืš ืฉื–ื” ื™ื”ื™ื” ืกื•ื›ืจ ืงื˜ืŸ ืฉืœื ื™ื”ืจื•ืก ืืช ื”ื˜ืขื ืฉืœ ื”ืžื ื”.
ื™ืฉ ืฉื™ืจื•ืช ืื™ืฉื•ืจ ืžืกืžื›ื™ื. ืžื™ืฉื”ื• ื™ื•ืฆืจ ืžืกืžืš ื•ืžื’ื™ืฉ ืื•ืชื• ืœื“ื™ื•ืŸ, ื‘ืžื”ืœื›ื• ืžืชื‘ืฆืขื•ืช ืขืจื™ื›ื•ืช, ื•ื‘ืกื•ืคื• ืฉืœ ื“ื‘ืจ ื”ืžืกืžืš ืžื•ืกื›ื. ืฉื™ืจื•ืช ื”ืื™ืฉื•ืจื™ื ืขืฆืžื• ืœื ื™ื•ื“ืข ื“ื‘ืจ ืขืœ ืžืกืžื›ื™ื: ื–ื” ืจืง ืฆ'ืื˜ ืฉืœ ืžืืฉืจื™ื ืขื ืคื•ื ืงืฆื™ื•ืช ืงื˜ื ื•ืช ื ื•ืกืคื•ืช ืฉืœื ื ืฉืงื•ืœ ื›ืืŸ.

ืœื›ืŸ, ื™ืฉื ื ื—ื“ืจื™ ืฆ'ืื˜ (ื”ืžืชืื™ืžื™ื ืœืžืกืžื›ื™ื) ืขื ืงื‘ื•ืฆื” ืžื•ื’ื“ืจืช ืžืจืืฉ ืฉืœ ืžืฉืชืชืคื™ื ื‘ื›ืœ ืื—ื“ ืžื”ื. ื›ืžื• ื‘ืฆ'ืื˜ื™ื ืจื’ื™ืœื™ื, ื”ื•ื“ืขื•ืช ืžื›ื™ืœื•ืช ื˜ืงืกื˜ ื•ืงื‘ืฆื™ื ื•ื™ื›ื•ืœื•ืช ืœื”ื™ื•ืช ืชืฉื•ื‘ื•ืช ืื• ื”ืขื‘ืจื•ืช:

data class ChatMessage(
  // nullable ั‚ะฐะบ ะบะฐะบ ะฟะพัะฒะปัะตั‚ัั ั‚ะพะปัŒะบะพ ะฟะพัะปะต persist
  val id: Long? = null,
  /** ะกัั‹ะปะบะฐ ะฝะฐ ะฐะฒั‚ะพั€ะฐ */
  val author: UserReference,
  /** ะกะพะพะฑั‰ะตะฝะธะต */
  val message: String,
  /** ะกัั‹ะปะบะธ ะฝะฐ ะฐั‚ั‚ะฐั‡ะธ */
  // ะธะท-ะทะฐ ะพัะพะฑะตะฝะฝะพัั‚ะตะน ัะฒัะทะบะธ JPA+ะกะฃะ‘ะ” ะฟั€ะพั‰ะต ะฟะพะดะดะตั€ะถะธะฒะฐั‚ัŒ ะธ null, ะธ ะฟัƒัั‚ั‹ะต ัะฟะธัะบะธ
  val files: List<FileReference>? = null,
  /** ะ•ัะปะธ ัะฒะปัะตั‚ัั ะพั‚ะฒะตั‚ะพะผ, ั‚ะพ ะทะดะตััŒ ะฑัƒะดะตั‚ ะพั€ะธะณะธะฝะฐะป */
  val replyTo: ChatMessage? = null,
  /** ะ•ัะปะธ ัะฒะปัะตั‚ัั ะฟะตั€ะตัั‹ะปะบะพะน, ั‚ะพ ะทะดะตััŒ ะฑัƒะดะตั‚ ะพั€ะธะณะธะฝะฐะป */
  val forwardFrom: ChatMessage? = null
)

ืงื™ืฉื•ืจื™ ืงื‘ืฆื™ื ื•ืžืฉืชืžืฉื™ื ื”ื ืงื™ืฉื•ืจื™ื ืœื“ื•ืžื™ื™ื ื™ื ืื—ืจื™ื. ื›ืืŸ ืื ื—ื ื• ื—ื™ื™ื ื›ื›ื”:

typealias FileReference Long
typealias UserReference Long

ื ืชื•ื ื™ ื”ืžืฉืชืžืฉ ืžืื•ื—ืกื ื™ื ื‘-Keycloak ื•ืžืชืงื‘ืœื™ื ื‘ืืžืฆืขื•ืช REST. ื›ืš ื’ื ืœื’ื‘ื™ ืงื‘ืฆื™ื: ืงื‘ืฆื™ื ื•ืžื˜ื-ืื™ื ืคื•ืจืžืฆื™ื” ืขืœื™ื”ื ื ืžืฆืื™ื ื‘ืฉื™ืจื•ืช ืื—ืกื•ืŸ ืงื‘ืฆื™ื ื ืคืจื“.

ื›ืœ ื”ืฉื™ื—ื•ืช ืœืฉื™ืจื•ืชื™ื ืืœื• ื”ื ื‘ืงืฉื•ืช ื›ื‘ื“ื•ืช. ื”ืžืฉืžืขื•ืช ื”ื™ื ืฉื”ืชืงื•ืจื” ืฉืœ ื”ืขื‘ืจืช ื‘ืงืฉื•ืช ืืœื• ื’ื“ื•ืœื” ื‘ื”ืจื‘ื” ืžื”ื–ืžืŸ ืฉืœื•ืงื— ืœืขื™ื‘ื•ื“ืŸ ืขืœ ื™ื“ื™ ืฉื™ืจื•ืช ืฆื“ ืฉืœื™ืฉื™. ืขืœ ืกืคืกืœื™ ื”ื‘ื“ื™ืงื” ืฉืœื ื•, ื–ืžืŸ ื”ื”ืชืงืฉืจื•ืช ื”ืื•ืคื™ื™ื ื™ ืœืฉื™ืจื•ืชื™ื ื›ืืœื” ื”ื•ื 100 ืืœืคื™ื•ืช ื”ืฉื ื™ื™ื”, ืื– ื ืฉืชืžืฉ ื‘ืžืกืคืจื™ื ืืœื” ื‘ื”ืžืฉืš.

ืื ื—ื ื• ืฆืจื™ื›ื™ื ืœื™ืฆื•ืจ ื‘ืงืจ REST ืคืฉื•ื˜ ื›ื“ื™ ืœืงื‘ืœ ืืช N ื”ื”ื•ื“ืขื•ืช ื”ืื—ืจื•ื ื•ืช ืขื ื›ืœ ื”ืžื™ื“ืข ื”ื“ืจื•ืฉ. ื›ืœื•ืžืจ, ืื ื• ืžืืžื™ื ื™ื ืฉื‘-frontend ืžื•ื“ืœ ื”ื”ื•ื“ืขื•ืช ื›ืžืขื˜ ื–ื”ื” ื•ื™ืฉ ืœืฉืœื•ื— ืืช ื›ืœ ื”ื ืชื•ื ื™ื. ื”ื”ื‘ื“ืœ ื‘ื™ืŸ ื”ืžื•ื“ืœ ื”ื—ื–ื™ืชื™ ื”ื•ื ืฉืฆืจื™ืš ืœื”ืฆื™ื’ ืืช ื”ืงื•ื‘ืฅ ื•ื”ืžืฉืชืžืฉ ื‘ืฆื•ืจื” ืžืขื˜ ืžืคื•ืขื ื—ืช ืขืœ ืžื ืช ืœื”ืคื•ืš ืื•ืชื ืœืงื™ืฉื•ืจื™ื:

/** ะ’ ั‚ะฐะบะพะผ ะฒะธะดะต ะพั‚ะดะฐัŽั‚ัั ััั‹ะปะบะธ ะฝะฐ ััƒั‰ะฝะพัั‚ะธ ะดะปั ั„ั€ะพะฝั‚ะฐ */
data class ReferenceUI(
  /** ะ˜ะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ ะดะปั url */
  val ref: String,
  /** ะ’ะธะดะธะผะพะต ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŽ ะฝะฐะทะฒะฐะฝะธะต ััั‹ะปะบะธ */
  val name: String
)
data class ChatMessageUI(
  val id: Long,
  /** ะกัั‹ะปะบะฐ ะฝะฐ ะฐะฒั‚ะพั€ะฐ */
  val author: ReferenceUI,
  /** ะกะพะพะฑั‰ะตะฝะธะต */
  val message: String,
  /** ะกัั‹ะปะบะธ ะฝะฐ ะฐั‚ั‚ะฐั‡ะธ */
  val files: List<ReferenceUI>,
  /** ะ•ัะปะธ ัะฒะปัั‚ัั ะพั‚ะฒะตั‚ะพะผ, ั‚ะพ ะทะดะตััŒ ะฑัƒะดะตั‚ ะพั€ะธะณะธะฝะฐะป */
  val replyTo: ChatMessageUI? = null,
  /** ะ•ัะปะธ ัะฒะปัั‚ัั ะฟะตั€ะตัั‹ะปะบะพะน, ั‚ะพ ะทะดะตััŒ ะฑัƒะดะตั‚ ะพั€ะธะณะธะฝะฐะป */
  val forwardFrom: ChatMessageUI? = null
)

ืื ื—ื ื• ืฆืจื™ื›ื™ื ืœื™ื™ืฉื ืืช ื”ื“ื‘ืจื™ื ื”ื‘ืื™ื:

interface ChatRestApi {
  fun getLast(nInt): List<ChatMessageUI>
}

ืžืžืฉืง ื”ืžืฉืชืžืฉ ืฉืœ Postfix ืคื™ืจื•ืฉื• ื“ื’ืžื™ DTO ืขื‘ื•ืจ ื”-frontend, ื›ืœื•ืžืจ, ืžื” ืฉืขืœื™ื ื• ืœืฉืจืช ื‘ืืžืฆืขื•ืช REST.

ืžื” ืฉืขืฉื•ื™ ืœื”ืคืชื™ืข ื›ืืŸ ื”ื•ื ืฉืื ื—ื ื• ืœื ืžืขื‘ื™ืจื™ื ืฉื•ื ืžื–ื”ื” ืฆ'ืื˜ ื•ืืคื™ืœื• ื‘ืžื•ื“ืœ ChatMessage/ChatMessageUI ืื™ืŸ ื›ื–ื”. ืขืฉื™ืชื™ ื–ืืช ื‘ื›ื•ื•ื ื” ื›ื“ื™ ืœื ืœื‘ืœื‘ืœ ืืช ื”ืงื•ื“ ืฉืœ ื”ื“ื•ื’ืžืื•ืช (ื”ืฆ'ืื˜ื™ื ืžื‘ื•ื“ื“ื™ื, ืื– ืืคืฉืจ ืœื”ื ื™ื— ืฉื™ืฉ ืœื ื• ืจืง ืื—ื“).

ืกื˜ื™ื™ื” ืคื™ืœื•ืกื•ืคื™ืชื’ื ื”ืžื—ืœืงื” ChatMessageUI ื•ื’ื ื”ืฉื™ื˜ื” ChatRestApi.getLast ืžืฉืชืžืฉื•ืช ื‘ืกื•ื’ ื”ื ืชื•ื ื™ื List, ื›ืืฉืจ ืœืžืขืฉื” ืžื“ื•ื‘ืจ ื‘ืกื˜ ืžืกื•ื“ืจ. ื‘-JDK ื›ืœ ื–ื” ืจืข, ื›ืš ืฉื”ื›ืจื–ื” ืขืœ ืกื“ืจ ื”ืืœืžื ื˜ื™ื ื‘ืจืžืช ื”ืžืžืฉืง (ืฉื™ืžื•ืจ ื”ืกื“ืจ ื‘ืขืช ื”ื”ื•ืกืคื” ื•ื”ืฉืœื™ืคื”) ืœื ืชืขื‘ื•ื“. ืื– ื–ื” ื”ืคืš ืœื ื•ื”ื’ ื ืคื•ืฅ ืœื”ืฉืชืžืฉ ื‘- List ื‘ืžืงืจื™ื ืฉื‘ื”ื ื™ืฉ ืฆื•ืจืš ื‘-Set ืžืกื•ื“ืจ (ื™ืฉ ื’ื LinkedHashSet, ืื‘ืœ ื–ื” ืœื ืžืžืฉืง).
ื”ื’ื‘ืœื” ื—ืฉื•ื‘ื”: ืื ื• ื ื ื™ื— ืฉืื™ืŸ ืฉืจืฉืจื•ืช ืืจื•ื›ื•ืช ืฉืœ ืชืฉื•ื‘ื•ืช ืื• ื”ืขื‘ืจื•ืช. ื›ืœื•ืžืจ, ื”ื ืงื™ื™ืžื™ื, ืื‘ืœ ืื•ืจื›ื ืื™ื ื• ืขื•ืœื” ืขืœ ืฉืœื•ืฉ ื”ื•ื“ืขื•ืช. ื™ืฉ ืœื”ืขื‘ื™ืจ ืืช ื›ืœ ืฉืจืฉืจืช ื”ื”ื•ื“ืขื•ืช ืœ-frontend.

ื›ื“ื™ ืœืงื‘ืœ ื ืชื•ื ื™ื ืžืฉื™ืจื•ืชื™ื ื—ื™ืฆื•ื ื™ื™ื ื™ืฉ ืืช ืžืžืฉืงื™ ื”-API ื”ื‘ืื™ื:

interface ChatMessageRepository {
  fun findLast(nInt): List<ChatMessage>
}
data class FileHeadRemote(
  val id: FileReference,
  val name: String
)
interface FileRemoteApi {
  fun getHeadById(idFileReference): FileHeadRemote
  fun getHeadsByIds(idSet<FileReference>): Set<FileHeadRemote>
  fun getHeadsByIds(idList<FileReference>): List<FileHeadRemote>
  fun getHeadsByChat(): List<FileHeadRemote>
}
data class UserRemote(
  val id: UserReference,
  val name: String
)
interface UserRemoteApi {
  fun getUserById(idUserReference): UserRemote
  fun getUsersByIds(idSet<UserReference>): Set<UserRemote>
  fun getUsersByIds(idList<UserReference>): List<UserRemote>
}

ื ื™ืชืŸ ืœืจืื•ืช ืฉืฉื™ืจื•ืชื™ื ื—ื™ืฆื•ื ื™ื™ื ืžืกืคืงื™ื ื‘ืชื—ื™ืœื” ืขื™ื‘ื•ื“ ืืฆื•ื•ื”, ื•ื‘ืฉืชื™ ื”ื’ืจืกืื•ืช: ื“ืจืš Set (ืœืœื ืฉื™ืžื•ืจ ืกื“ืจ ื”ืืœืžื ื˜ื™ื, ืขื ืžืคืชื—ื•ืช ื™ื™ื—ื•ื“ื™ื™ื) ื•ื“ืจืš List (ื™ื™ืชื›ื ื• ื›ืคื™ืœื•ื™ื•ืช - ื”ืกื“ืจ ื ืฉืžืจ).

ื™ื™ืฉื•ืžื™ื ืคืฉื•ื˜ื™ื

ื™ื™ืฉื•ื ื ืื™ื‘ื™

ื”ื™ื™ืฉื•ื ื”ื ืื™ื‘ื™ ื”ืจืืฉื•ืŸ ืฉืœ ื‘ืงืจ ื”-REST ืฉืœื ื• ื™ื™ืจืื” ื‘ืขืจืš ื›ืš ื‘ืจื•ื‘ ื”ืžืงืจื™ื:

class ChatRestController(
  private val messageRepository: ChatMessageRepository,
  private val userRepository: UserRemoteApi,
  private val fileRepository: FileRemoteApi
) : ChatRestApi {
  override fun getLast(nInt) =
    messageRepository.findLast(n)
      .map it.toFrontModel() }
  
  private fun ChatMessage.toFrontModel(): ChatMessageUI =
    ChatMessageUI(
      id = id ?: throw IllegalStateException("$this must be persisted"),
      author = userRepository.getUserById(author).toFrontReference(),
      message = message,
      files = files?.let files ->
        fileRepository.getHeadsByIds(files)
          .map it.toFrontReference() }
      } ?: listOf(),
      forwardFrom = forwardFrom?.toFrontModel(),
      replyTo = replyTo?.toFrontModel()
    )
}

ื”ื›ืœ ืžืื•ื“ ื‘ืจื•ืจ, ื•ื–ื” ื™ืชืจื•ืŸ ื’ื“ื•ืœ.

ืื ื• ืžืฉืชืžืฉื™ื ื‘ืขื™ื‘ื•ื“ ืืฆื•ื•ื” ื•ืžืงื‘ืœื™ื ื ืชื•ื ื™ื ืžืฉื™ืจื•ืช ื—ื™ืฆื•ื ื™ ื‘ืืฆื•ื•ืช. ืื‘ืœ ืžื” ืงื•ืจื” ืœืคืจื•ื“ื•ืงื˜ื™ื‘ื™ื•ืช ืฉืœื ื•?

ืขื‘ื•ืจ ื›ืœ ื”ื•ื“ืขื”, ืชืชื‘ืฆืข ืงืจื™ืื” ืื—ืช ืœ-UserRemoteApi ื›ื“ื™ ืœืงื‘ืœ ื ืชื•ื ื™ื ืขืœ ืฉื“ื” ื”ืžื—ื‘ืจ ื•ืงืจื™ืื” ืื—ืช ืœ-FileRemoteApi ื›ื“ื™ ืœืงื‘ืœ ืืช ื›ืœ ื”ืงื‘ืฆื™ื ื”ืžืฆื•ืจืคื™ื. ื ืจืื” ืฉื–ื”ื• ื–ื”. ื ื ื™ื— ืฉื”ืฉื“ื•ืช forwardFrom ื•- replyTo ืขื‘ื•ืจ ChatMessage ืžืชืงื‘ืœื™ื ื‘ืฆื•ืจื” ื›ื–ื• ืฉื–ื” ืœื ืžืฆืจื™ืš ืฉื™ื—ื•ืช ืžื™ื•ืชืจื•ืช. ืื‘ืœ ื”ืคื™ื›ืชื ืœ-ChatMessageUI ืชื•ื‘ื™ืœ ืœืจืงื•ืจืกื™ื”, ื›ืœื•ืžืจ, ืžื•ื ื™ ื”ืฉื™ื—ื•ืช ื™ื›ื•ืœื™ื ืœื”ื’ื“ื™ืœ ื‘ืื•ืคืŸ ืžืฉืžืขื•ืชื™. ื›ืคื™ ืฉืฆื™ื™ื ื• ืงื•ื“ื ืœื›ืŸ, ื ื ื™ื— ืฉืื™ืŸ ืœื ื• ื”ืจื‘ื” ืงื™ื ื•ืŸ ื•ื”ืฉืจืฉืจืช ืžื•ื’ื‘ืœืช ืœืฉืœื•ืฉ ื”ื•ื“ืขื•ืช.

ื›ืชื•ืฆืื” ืžื›ืš ื ืงื‘ืœ ื‘ื™ืŸ ืฉืชื™ื™ื ืœืฉืฉ ืฉื™ื—ื•ืช ืœืฉื™ืจื•ืชื™ื ื—ื™ืฆื•ื ื™ื™ื ืœื›ืœ ื”ื•ื“ืขื” ื•ืฉื™ื—ืช JPA ืื—ืช ืœื›ืœ ื—ื‘ื™ืœืช ื”ื”ื•ื“ืขื•ืช. ื”ืžืกืคืจ ื”ื›ื•ืœืœ ืฉืœ ื”ืฉื™ื—ื•ืช ื™ืฉืชื ื” ื‘ื™ืŸ 2*N+1 ืœ-6*N+1. ื›ืžื” ื–ื” ื‘ื™ื—ื™ื“ื•ืช ืืžื™ืชื™ื•ืช? ื ื ื™ื— ืฉืœื•ืงื— 20 ื”ื•ื“ืขื•ืช ืœืขื™ื‘ื•ื“ ื“ืฃ. ื›ื“ื™ ืœื”ืฉื™ื’ ืื•ืชื, ืชืฆื˜ืจืš ืž 4 ืฉื ื™ื•ืช ืขื“ 10 ืฉื ื™ื•ืช. ื ื•ืจื! ืื ื™ ืจื•ืฆื” ืœืฉืžื•ืจ ืื•ืชื• ื‘ื˜ื•ื•ื— ืฉืœ 500 ืืœืคื™ื•ืช ื”ืฉื ื™ื™ื”. ื•ืžื›ื™ื•ื•ืŸ ืฉื”ื ื—ืœืžื• ืœืขืฉื•ืช ื’ืœื™ืœื” ื—ืœืงื” ื‘ื—ื–ื™ืช, ื ื™ืชืŸ ืœื”ื›ืคื™ืœ ืืช ื“ืจื™ืฉื•ืช ื”ื‘ื™ืฆื•ืขื™ื ืขื‘ื•ืจ ื ืงื•ื“ืช ืงืฆื” ื–ื•.

ื™ืชืจื•ื ื•ืช:

  1. ื”ืงื•ื“ ืžืชื•ืžืฆืช ื•ืžืชืขื“ ื‘ืขืฆืžื• (ื—ืœื•ื ืฉืœ ืฆื•ื•ืช ืชืžื™ื›ื”).
  2. ื”ืงื•ื“ ืคืฉื•ื˜, ื›ืš ืฉืื™ืŸ ื›ืžืขื˜ ื”ื–ื“ืžื ื•ื™ื•ืช ืœื™ืจื•ืช ืœืขืฆืžืš ื‘ืจื’ืœ.
  3. ืขื™ื‘ื•ื“ ืืฆื•ื•ื” ืœื ื ืจืื” ื›ืžื• ืžืฉื”ื• ื–ืจ ื•ื”ื•ื ืžืฉื•ืœื‘ ื‘ืื•ืคืŸ ืื•ืจื’ื ื™ ื‘ืœื•ื’ื™ืงื”.
  4. ืฉื™ื ื•ื™ื™ื ืœื•ื’ื™ื™ื ื™ื”ื™ื• ืงืœื™ื ืœื‘ื™ืฆื•ืข ื•ื™ื”ื™ื• ืžืงื•ืžื™ื™ื.

ืžื™ื ื•ืก:

ื‘ื™ืฆื•ืขื™ื ืื™ื•ืžื™ื ื‘ื’ืœืœ ื—ื‘ื™ืœื•ืช ืงื˜ื ื•ืช ืžืื•ื“.

ื ื™ืชืŸ ืœืจืื•ืช ื’ื™ืฉื” ื–ื• ืœืขืชื™ื ืงืจื•ื‘ื•ืช ืœืžื“ื™ ื‘ืฉื™ืจื•ืชื™ื ืคืฉื•ื˜ื™ื ืื• ื‘ืื‘ื•ืช ื˜ื™ืคื•ืก. ืื ืžื”ื™ืจื•ืช ื‘ื™ืฆื•ืข ื”ืฉื™ื ื•ื™ื™ื ื—ืฉื•ื‘ื”, ื‘ืงื•ืฉื™ ื›ื“ืื™ ืœืกื‘ืš ืืช ื”ืžืขืจื›ืช. ื™ื—ื“ ืขื ื–ืืช, ืขื‘ื•ืจ ื”ืฉื™ืจื•ืช ื”ืžืื•ื“ ืคืฉื•ื˜ ืฉืœื ื• ื”ื‘ื™ืฆื•ืขื™ื ื ื•ืจืื™ื™ื, ื•ืœื›ืŸ ื”ื™ืงืฃ ื”ืชื—ื•ืœื” ืฉืœ ื’ื™ืฉื” ื–ื• ื”ื•ื ืฆืจ ืžืื•ื“.

ืขื™ื‘ื•ื“ ืžืงื‘ื™ืœ ื ืื™ื‘ื™

ืืชื” ื™ื›ื•ืœ ืœื”ืชื—ื™ืœ ืœืขื‘ื“ ืืช ื›ืœ ื”ื”ื•ื“ืขื•ืช ื‘ืžืงื‘ื™ืœ - ื–ื” ื™ืืคืฉืจ ืœืš ืœื”ื™ืคื˜ืจ ืžื”ืขืœื™ื™ื” ื”ืœื™ื ื™ืืจื™ืช ื‘ื–ืžืŸ ื‘ื”ืชืื ืœืžืกืคืจ ื”ื”ื•ื“ืขื•ืช. ื–ื” ืœื ื ืชื™ื‘ ื˜ื•ื‘ ื‘ืžื™ื•ื—ื“ ื›ื™ ื–ื” ื™ื’ืจื•ื ืœืขื•ืžืก ืฉื™ื ื’ื“ื•ืœ ืขืœ ื”ืฉื™ืจื•ืช ื”ื—ื™ืฆื•ื ื™.

ื™ื™ืฉื•ื ืขื™ื‘ื•ื“ ืžืงื‘ื™ืœ ื”ื•ื ืคืฉื•ื˜ ืžืื•ื“:

override fun getLast(nInt) =
  messageRepository.findLast(n).parallelStream()
    .map it.toFrontModel() }
    .collect(toList())

ื‘ืืžืฆืขื•ืช ืขื™ื‘ื•ื“ ื”ื•ื“ืขื•ืช ืžืงื‘ื™ืœ, ืื ื• ืžืงื‘ืœื™ื 300โ€“700 ืืœืคื™ื•ืช ื”ืฉื ื™ื™ื” ื‘ืื•ืคืŸ ืื™ื“ื™ืืœื™, ื•ื–ื” ื”ืจื‘ื” ื™ื•ืชืจ ื˜ื•ื‘ ืžืืฉืจ ื‘ื™ื™ืฉื•ื ื ืื™ื‘ื™, ืื‘ืœ ืขื“ื™ื™ืŸ ืœื ืžืกืคื™ืง ืžื”ื™ืจ.

ืขื ื’ื™ืฉื” ื–ื•, ื‘ืงืฉื•ืช ืœ-userRepository ื•ืœ-fileRepository ื™ื‘ื•ืฆืขื• ื‘ืื•ืคืŸ ืกื™ื ื›ืจื•ื ื™, ื•ื–ื” ืœื ืžืื•ื“ ื™ืขื™ืœ. ื›ื“ื™ ืœืชืงืŸ ื–ืืช, ืชืฆื˜ืจืš ืœืฉื ื•ืช ืืช ื”ื™ื’ื™ื•ืŸ ื”ืฉื™ื—ื” ื“ื™ ื”ืจื‘ื”. ืœื“ื•ื’ืžื”, ื“ืจืš CompletionStage (ื”ืžื›ื•ื ื” CompletableFuture):

private fun ChatMessage.toFrontModel(): ChatMessageUI =
  CompletableFuture.supplyAsync {
    userRepository.getUserById(author).toFrontReference()
  }.thenCombine(
    files?.let {
      CompletableFuture.supplyAsync {
        fileRepository.getHeadsByIds(files).map it.toFrontReference() }
      }
    } ?: CompletableFuture.completedFuture(listOf())
  ) authorfiles ->
    ChatMessageUI(
      id = id ?: throw IllegalStateException("$this must be persisted"),
      author = author,
      message = message,
      files = files,
      forwardFrom = forwardFrom?.toFrontModel(),
      replyTo = replyTo?.toFrontModel()
    )
  }.get()!!

ื ื™ืชืŸ ืœืจืื•ืช ืฉืงื•ื“ ื”ืžื™ืคื•ื™ ื”ืคืฉื•ื˜ ื‘ื”ืชื—ืœื” ื”ืคืš ืคื—ื•ืช ืžื•ื‘ืŸ. ื”ืกื™ื‘ื” ืœื›ืš ื”ื™ื ืฉื”ื™ื™ื ื• ืฆืจื™ื›ื™ื ืœื”ืคืจื™ื“ ื‘ื™ืŸ ืฉื™ื—ื•ืช ืœืฉื™ืจื•ืชื™ื ื—ื™ืฆื•ื ื™ื™ื ืžื”ืžืงื•ื ืฉื‘ื• ื ืขืฉื” ืฉื™ืžื•ืฉ ื‘ืชื•ืฆืื•ืช. ื–ื” ื›ืฉืœืขืฆืžื• ืœื ืจืข. ืื‘ืœ ืฉื™ืœื•ื‘ ืฉื™ื—ื•ืช ืœื ื ืจืื” ืืœื’ื ื˜ื™ ื‘ืžื™ื•ื—ื“ ื•ื“ื•ืžื” ืœ"ืื˜ืจื™ื•ืช" ืชื’ื•ื‘ืชื™ ื˜ื™ืคื•ืกื™.

ืื ืืชื” ืžืฉืชืžืฉ ื‘ืงื•ืจื•ื˜ื™ื ื•ืช, ื”ื›ืœ ื™ื™ืจืื” ื”ื’ื•ืŸ ื™ื•ืชืจ:

private fun ChatMessage.toFrontModel(): ChatMessageUI =
  join(
    userRepository.getUserById(author).toFrontReference() },
    files?.let fileRepository.getHeadsByIds(files)
      .map it.toFrontReference() } } ?: listOf() }
  ).let (author, files) ->
    ChatMessageUI(
      id = id ?: throw IllegalStateException("$this must be persisted"),
      author = author,
      message = message,
      files = files,
      forwardFrom = forwardFrom?.toFrontModel(),
      replyTo = replyTo?.toFrontModel()
    )
  }

ืื™ืคื”:

fun <ABjoin(a: () -> Ab: () -> B) =
  runBlocking(IO{
    awaitAll(async a() }async b() })
  }.let {
    it[0as to it[1as B
  }

ืชื™ืื•ืจื˜ื™ืช, ื‘ืืžืฆืขื•ืช ืขื™ื‘ื•ื“ ืžืงื‘ื™ืœ ื›ื–ื”, ื ืงื‘ืœ 200โ€“400 ืืœืคื™ื•ืช ื”ืฉื ื™ื™ื”, ืฉื–ื” ื›ื‘ืจ ืงืจื•ื‘ ืœืฆื™ืคื™ื•ืช ืฉืœื ื•.

ืœืžืจื‘ื” ื”ืฆืขืจ, ื”ืงื‘ืœื” ื›ื” ื˜ื•ื‘ื” ืœื ืžืชืจื—ืฉืช, ื•ื”ืžื—ื™ืจ ืœืชืฉืœื•ื ื”ื•ื ื“ื™ ืื›ื–ืจื™: ื›ืฉืจืง ืžืฉืชืžืฉื™ื ื‘ื•ื“ื“ื™ื ื™ืขื‘ื“ื• ื‘ื• ื–ืžื ื™ืช, ื”ืฉื™ืจื•ืชื™ื ื™ื™ืคื’ืขื• ื‘ืฉื˜ืฃ ืฉืœ ื‘ืงืฉื•ืช ืฉืœื ื™ื˜ื•ืคืœื• ื‘ืžืงื‘ื™ืœ, ื›ืš ืฉืื ื• ื™ื—ื–ื•ืจ ืœ-4 ื”ืขืฆื•ื‘ื™ื ืฉืœื ื•.

ื”ืชื•ืฆืื” ืฉืœื™ ื‘ืฉื™ืžื•ืฉ ื‘ืฉื™ืจื•ืช ื›ื–ื” ื”ื™ื 1300โ€“1700 ืืœืคื™ื•ืช ื”ืฉื ื™ื™ื” ืœืขื™ื‘ื•ื“ 20 ื”ื•ื“ืขื•ืช. ื–ื” ืžื”ื™ืจ ื™ื•ืชืจ ืžืืฉืจ ื‘ื™ื™ืฉื•ื ื”ืจืืฉื•ืŸ, ืื‘ืœ ืขื“ื™ื™ืŸ ืœื ืคื•ืชืจ ืืช ื”ื‘ืขื™ื”.

ืฉื™ืžื•ืฉื™ื ื—ืœื•ืคื™ื™ื ื‘ืฉืื™ืœืชื•ืช ืžืงื‘ื™ืœื•ืชืžื” ืื ืฉื™ืจื•ืชื™ ืฆื“ ืฉืœื™ืฉื™ ืื™ื ื ืžืกืคืงื™ื ืขื™ื‘ื•ื“ ืืฆื•ื•ื”? ืœื“ื•ื’ืžื”, ืืชื” ื™ื›ื•ืœ ืœื”ืกืชื™ืจ ืืช ื”ื™ืขื“ืจ ื”ื˜ืžืขืช ืขื™ื‘ื•ื“ ืืฆื•ื•ื” ื‘ืชื•ืš ืฉื™ื˜ื•ืช ืžืžืฉืง:

interface UserRemoteApi {
  fun getUserById(idUserReference): UserRemote
  fun getUsersByIds(idSet<UserReference>): Set<UserRemote> =
    id.parallelStream()
      .map getUserById(it}.collect(toSet())
  fun getUsersByIds(idList<UserReference>): List<UserRemote> =
    id.parallelStream()
      .map getUserById(it}.collect(toList())
}

ื–ื” ื”ื’ื™ื•ื ื™ ืื ืืชื” ืžืงื•ื•ื” ืœืจืื•ืช ืขื™ื‘ื•ื“ ืืฆื•ื•ื” ื‘ื’ืจืกืื•ืช ืขืชื™ื“ื™ื•ืช.
ื™ืชืจื•ื ื•ืช:

  1. ื”ื˜ืžืข ื‘ืงืœื•ืช ืขื™ื‘ื•ื“ ืžืงื‘ื™ืœ ืžื‘ื•ืกืก ื”ื•ื“ืขื•ืช.
  2. ืžื“ืจื’ื™ื•ืช ื˜ื•ื‘ื”.

ื—ืกืจื•ื ื•ืช:

  1. ื”ืฆื•ืจืš ืœื”ืคืจื™ื“ ื‘ื™ืŸ ืจื›ื™ืฉืช ื”ื ืชื•ื ื™ื ืœืขื™ื‘ื•ื“ื ื‘ืขืช ืขื™ื‘ื•ื“ ื‘ืงืฉื•ืช ืœืฉื™ืจื•ืชื™ื ืฉื•ื ื™ื ื‘ืžืงื‘ื™ืœ.
  2. ืขื•ืžืก ืžื•ื’ื‘ืจ ืขืœ ืฉื™ืจื•ืชื™ ืฆื“ ืฉืœื™ืฉื™.

ื ื™ืชืŸ ืœืจืื•ืช ื›ื™ ื”ื™ืงืฃ ื”ืชื—ื•ืœื” ื–ื”ื” ื‘ืงื™ืจื•ื‘ ืœื–ื” ืฉืœ ื”ื’ื™ืฉื” ื”ื ืื™ื‘ื™ืช. ื”ื’ื™ื•ื ื™ ืœื”ืฉืชืžืฉ ื‘ืฉื™ื˜ืช ื”ื‘ืงืฉื” ื”ืžืงื‘ื™ืœื” ืื ืืชื” ืจื•ืฆื” ืœื”ื’ื‘ื™ืจ ืืช ื‘ื™ืฆื•ืขื™ ื”ืฉื™ืจื•ืช ืฉืœืš ืžืกืคืจ ืคืขืžื™ื ื‘ื’ืœืœ ื ื™ืฆื•ืœ ื—ืกืจ ืจื—ืžื™ื ืฉืœ ืื—ืจื™ื. ื‘ื“ื•ื’ืžื” ืฉืœื ื•, ื”ืคืจื•ื“ื•ืงื˜ื™ื‘ื™ื•ืช ื’ื“ืœื” ืคื™ 2,5, ืืš ื‘ืจื•ืจ ืฉื–ื” ืœื ืžืกืคื™ืง.

ืฉืžื™ืจื” ื‘ืžื˜ืžื•ืŸ

ืืชื” ื™ื›ื•ืœ ืœืขืฉื•ืช ืžื˜ืžื•ืŸ ื‘ืจื•ื— JPA ืขื‘ื•ืจ ืฉื™ืจื•ืชื™ื ื—ื™ืฆื•ื ื™ื™ื, ื›ืœื•ืžืจ ืœืื—ืกืŸ ืื•ื‘ื™ื™ืงื˜ื™ื ืฉื”ืชืงื‘ืœื• ื‘ืชื•ืš ื”ืคืขืœื” ื›ื“ื™ ืœื ืœืงื‘ืœ ืื•ืชื ืฉื•ื‘ (ื›ื•ืœืœ ื‘ืžื”ืœืš ืขื™ื‘ื•ื“ ืืฆื•ื•ื”). ืืชื” ื™ื›ื•ืœ ืœื™ืฆื•ืจ ืžื˜ืžื•ื ื™ื ื›ืืœื” ื‘ืขืฆืžืš, ืืชื” ื™ื›ื•ืœ ืœื”ืฉืชืžืฉ ื‘-Spring ืขื @Cacheable ืฉืœื•, ื‘ื ื•ืกืฃ ืืชื” ืชืžื™ื“ ื™ื›ื•ืœ ืœื”ืฉืชืžืฉ ื‘ืžื˜ืžื•ืŸ ืžื•ื›ืŸ ื›ืžื• EhCache ื‘ืื•ืคืŸ ื™ื“ื ื™.

ื‘ืขื™ื” ื ืคื•ืฆื” ืชื”ื™ื” ืฉืžื˜ืžื•ื ื™ื ืฉื™ืžื•ืฉื™ื™ื ืจืง ืื ื™ืฉ ืœื”ื ื”ื™ื˜ื™ื. ื‘ืžืงืจื” ืฉืœื ื•, ืกื‘ื™ืจื•ืช ื’ื‘ื•ื”ื” ืœื”ื™ื˜ื™ื ื‘ืฉื“ื” ื”ืžื—ื‘ืจ (ื ื ื™ื—, 50%), ืื‘ืœ ืœื ื™ื”ื™ื• ื›ื ื™ืกื•ืช ืœืงื‘ืฆื™ื ื‘ื›ืœืœ. ื’ื™ืฉื” ื–ื• ืชืกืคืง ื›ืžื” ืฉื™ืคื•ืจื™ื, ืื‘ืœ ื”ื™ื ืœื ืชืฉื ื” ื‘ืื•ืคืŸ ืงื™ืฆื•ื ื™ ืืช ื”ืคืจื•ื“ื•ืงื˜ื™ื‘ื™ื•ืช (ื•ืื ื—ื ื• ืฆืจื™ื›ื™ื ืคืจื™ืฆืช ื“ืจืš).

ืžื˜ืžื•ื ื™ื ืฉืœ intersession (ืืจื•ื›ื™ื) ื“ื•ืจืฉื™ื ืœื•ื’ื™ืงื” ืžื•ืจื›ื‘ืช ืฉืœ ืื™ ืชื•ืงืฃ. ื‘ืื•ืคืŸ ื›ืœืœื™, ื›ื›ืœ ืฉืชื’ื™ืข ืžืื•ื—ืจ ื™ื•ืชืจ ืœืคืชืจื•ืŸ ื‘ืขื™ื•ืช ื‘ื™ืฆื•ืขื™ื ื‘ืืžืฆืขื•ืช ืžื˜ืžื•ื ื™ื ืฉืœ intersession, ื›ืš ื™ื™ื˜ื‘.

ื™ืชืจื•ื ื•ืช:

  1. ื™ื™ืฉื ืžื˜ืžื•ืŸ ืœืœื ืฉื™ื ื•ื™ ืงื•ื“.
  2. ืคืจื•ื“ื•ืงื˜ื™ื‘ื™ื•ืช ืžื•ื’ื‘ืจืช ืžืกืคืจ ืคืขืžื™ื (ื‘ืžืงืจื™ื ืžืกื•ื™ืžื™ื).

ื—ืกืจื•ื ื•ืช:

  1. ืืคืฉืจื•ืช ืœื‘ื™ืฆื•ืขื™ื ืžื•ืคื—ืชื™ื ื‘ืฉื™ืžื•ืฉ ืœื ื ื›ื•ืŸ.
  2. ื–ื™ื›ืจื•ืŸ ื’ื“ื•ืœ ืžืขืœ ื”ืจืืฉ, ื‘ืžื™ื•ื—ื“ ืขื ืžื˜ืžื•ื ื™ื ืืจื•ื›ื™ื.
  3. ืื™ ืชื•ืงืฃ ืžื•ืจื›ื‘, ืฉื’ื™ืื•ืช ื‘ื”ืŸ ื™ื•ื‘ื™ืœื• ืœื‘ืขื™ื•ืช ืฉืงืฉื” ืœืฉื—ื–ืจ ื‘ื–ืžืŸ ืจื™ืฆื”.

ืœืขืชื™ื ืงืจื•ื‘ื•ืช ืžืื•ื“, ืžื˜ืžื•ื ื™ื ืžืฉืžืฉื™ื ืจืง ื›ื“ื™ ืœืชืงืŸ ื‘ืžื”ื™ืจื•ืช ื‘ืขื™ื•ืช ืขื™ืฆื•ื‘. ื–ื” ืœื ืื•ืžืจ ืฉืืกื•ืจ ืœื”ืฉืชืžืฉ ื‘ื”ื. ืขื ื–ืืช, ืชืžื™ื“ ื›ื“ืื™ ืœื”ืชื™ื™ื—ืก ืืœื™ื”ื ื‘ื–ื”ื™ืจื•ืช ื•ืœื”ืขืจื™ืš ืชื—ื™ืœื” ืืช ืจื•ื•ื— ื”ื‘ื™ืฆื•ืขื™ื ืฉื ื•ืฆืจ, ื•ืจืง ืœืื—ืจ ืžื›ืŸ ืœืงื‘ืœ ื”ื—ืœื˜ื”.

ื‘ื“ื•ื’ืžื” ืฉืœื ื•, ืžื˜ืžื•ื ื™ื ื™ืกืคืงื• ืขืœื™ื™ืช ื‘ื™ืฆื•ืขื™ื ืฉืœ ื›-25%. ื™ื—ื“ ืขื ื–ืืช, ืœืžื˜ืžื•ื ื™ื ื™ืฉ ื“ื™ ื”ืจื‘ื” ื—ืกืจื•ื ื•ืช, ืื– ืœื ื”ื™ื™ืชื™ ืžืฉืชืžืฉ ื‘ื”ื ื›ืืŸ.

ืชื•ืฆืื•ืช ืฉืœ

ืื–, ื‘ื“ืงื ื• ื™ื™ืฉื•ื ื ืื™ื‘ื™ ืฉืœ ืฉื™ืจื•ืช ื”ืžืฉืชืžืฉ ื‘ืขื™ื‘ื•ื“ ืืฆื•ื•ื”, ื•ื›ืžื” ื“ืจื›ื™ื ืคืฉื•ื˜ื•ืช ืœื”ืื™ืฅ ืื•ืชื•.

ื”ื™ืชืจื•ืŸ ื”ืขื™ืงืจื™ ืฉืœ ื›ืœ ื”ืฉื™ื˜ื•ืช ื”ืœืœื• ื”ื•ื ื”ืคืฉื˜ื•ืช, ืฉืžืžื ื” ื™ืฉ ื”ืฉืœื›ื•ืช ื ืขื™ืžื•ืช ืจื‘ื•ืช.

ื‘ืขื™ื” ื ืคื•ืฆื” ื‘ืฉื™ื˜ื•ืช ืืœื• ื”ื™ื ื‘ื™ืฆื•ืขื™ื ื’ืจื•ืขื™ื, ื”ืงืฉื•ืจื™ื ื‘ืขื™ืงืจ ืœื’ื•ื“ืœ ื”ื—ื‘ื™ืœื•ืช. ืœื›ืŸ, ืื ื”ืคืชืจื•ื ื•ืช ื”ืืœื” ืœื ืžืชืื™ืžื™ื ืœืš, ืื– ื›ื“ืื™ ืœืฉืงื•ืœ ืฉื™ื˜ื•ืช ืจื“ื™ืงืœื™ื•ืช ื™ื•ืชืจ.

ื™ืฉื ื ืฉื ื™ ื›ื™ื•ื•ื ื™ื ืขื™ืงืจื™ื™ื ืฉื‘ื”ื ื ื™ืชืŸ ืœื—ืคืฉ ืคืชืจื•ื ื•ืช:

  • ืขื‘ื•ื“ื” ืืกื™ื ื›ืจื•ื ื™ืช ืขื ื ืชื•ื ื™ื (ื“ื•ืจืฉืช ืฉื™ื ื•ื™ ืคืจื“ื™ื’ืžื”, ื•ืœื›ืŸ ืœื ื ื“ื•ืŸ ื‘ืžืืžืจ ื–ื”);
  • ื”ื’ื“ืœื” ืฉืœ ืืฆื•ื•ืช ืชื•ืš ืฉืžื™ืจื” ืขืœ ืขื™ื‘ื•ื“ ืกื™ื ื›ืจื•ื ื™.

ื”ื’ื“ืœื” ืฉืœ ืืฆื•ื•ืช ืชืคื—ื™ืช ืžืื•ื“ ืืช ืžืกืคืจ ื”ืฉื™ื—ื•ืช ื”ื—ื™ืฆื•ื ื™ื•ืช ื•ื‘ืžืงื‘ื™ืœ ืชืฉืื™ืจ ืืช ื”ืงื•ื“ ืกื™ื ื›ืจื•ื ื™. ื”ื—ืœืง ื”ื‘ื ืฉืœ ื”ืžืืžืจ ื™ื•ืงื“ืฉ ืœื ื•ืฉื ื–ื”.

ืžืงื•ืจ: www.habr.com

ื”ื•ืกืคืช ืชื’ื•ื‘ื”