Контракты#

Контракты — это декларации интерфейсов, которые используются для разрыва бинарной зависимости между модулями. Вместо прямой зависимости от реализации модуля A модуль B зависит только от контрактов модуля A. Это позволяет изменять реализацию модуля A без перекомпиляции модуля B.

Контракты содержат все методы из Dpi и аннотированные программистом методы из Api.

Схема генерации контрактов

Адаптеры#

Адаптеры — связующее звено, которое позволяет контрактам работать с Api, Pkg, Lib. Генерируются вместе с контрактами и имеют следующие расширения: Apa, Pka, Lia.

Аннотации#

Для декларации контрактов используются аннотации:

  • ExternalApi — для апи;

  • ExternalLib — для библиотек;

  • ExternalPkg — для пакетов.

Аннотации для методов:

  • CntInclude — сообщает, что для этого метода необходимо сгенерировать контракт;

  • CntExclude — сообщает, что для этого метода не нужно генерировать контракт.

Аннотация декларации контрактов имеет правило defaultRule, от которого зависит генерация методов в контракте (значение по умолчанию Include):

  • MethodScope.Include — контракты генерируются для всех публичных методов, которые не отмечены CntExclude;

  • MethodScope.Exclude — контракты генерируются только для публичных методов, которые отмечены аннотацией CntInclude.

Пример декларации контрактов:

@ExternalApi(defaultRule = MethodScope.Include)
class Btk_FileApi {
  // Приватный метод не будет сгенерирован в любом сценарии
  private def doSomething(): Unit = {

  }

  // Не будет сгенерирован из-за аннотации
  @CntExclude
  def withFile(fileStorage: FileStorage, fullFileName: NString)(f: File => Unit): Unit = {
    val fileSrc = fileStorage.FileFactory(fullFileName)
    if (fileSrc.exists()) {
      f(fileSrc)
    } else {
      throw AppException(s"Файл $fullFileName был перемещен или удален из хранилища")
    }
  }

  // Будет сгенерирован из-за аннотации
  @CntInclude
  def getFileStorageByFile(idpFile: NLong): NString = {
    if (idpFile.isNotNull) {
      val ropFile = Btk_FileApi().load(idpFile)
      val idFileStorage = ropFile.get(_.idFileStorage)
      Btk_FileStorageApi().getMnemoCode(idFileStorage).getOrElse("Default")
    } else {
      throw AppException("Не передан идентификатор файла")
    }
  }
}

Генерация контрактов#

После проставления аннотаций запустите генерацию. Процесс выполняется аналогично генерации исходного кода (source generator). Для этого необходимо:

  1. Установить новую версию плагина для инструкции по установке плагина для IDEA и обновить external-tools на главном экране IDEA;

  2. Включить флаг shouldGenerateContract: true в конфигурации пакетов сборки модуля;

  3. Проставить аннотации на Api, Pkg, Lib;

  4. Сгенерировать контракты по необходимым файлам;

  5. Сгенерировать по этим же файлам исходный код.

Результат генерации по Api#

Пример сгенерированного контракта Btk_FileApc:

Внимание

Все методы, объявленные только в Dpi, автоматически попадают в контракт, их не нужно аннотировать.

trait Btk_FileDpc[
  ARC <: Btk_FileDrc,
  ATC <: Btk_FileDtc[ARC, Api[java.lang.Long, ARC], ATC],
  APC <: Btk_FileApc
] extends RootApc[java.lang.Long, ARC, APC]
  with JIdExtApc[EntityAbst, java.lang.Long, ARC]
  with AttrApc[java.lang.Long, ARC]


trait Btk_FileApc extends Btk_FileDpc[Btk_FileDrc, Btk_FileAtc, Btk_FileApc] {

  
  def copyObject(idFrom: NLong, idTo: NLong, idParent: NLong, params: Btk_CopyObjectParam): NLong

  
  def copyObject(idFrom: NLong, idTo: NLong, idParent: NLong): NLong

  
  def setgidSrc(rop: ApiRop, value: NGid): Unit

  
  def setsFileName(rop: ApiRop, value: NString): Unit

  
  def setsExt(rop: ApiRop, value: NString): Unit

  
  def setsFullFileName(rop: ApiRop, value: NString): Unit

  
  def setidFileStorage(rop: ApiRop, value: NLong): Unit

  
  def setsSHA2(rop: ApiRop, value: NString): Unit

  
  def setsMD5(rop: ApiRop, value: NString): Unit

  
  def setnSize(rop: ApiRop, value: NNumber): Unit

  
  def setidAttachType(rop: ApiRop, value: NLong): Unit

  
  def setidSrcObjectType(rop: ApiRop, value: NLong): Unit

  
  def setbManualMoved(rop: ApiRop, value: NNumber): Unit

  
  def setbDeleted(rop: ApiRop, value: NNumber): Unit

  
  def setjParams(rop: ApiRop, value: NString): Unit

  
  def parsejParams(rop: ApiRop): JESegment

  def getFileStorageByFile(idpFile: NLong): NString
}

object Btk_FileApc extends ApcFactory[Btk_FileDrc, Btk_FileApc] {
  override protected val sApiClassPath: String = "ru.bitec.app.btk.Btk_FileApi$"
  override protected val sApaClassPath: String = "ru.bitec.app.btk.Btk_FileApa"
}

trait Btk_FileAtc extends Btk_FileDtc[Btk_FileDrc, Api[java.lang.Long, Btk_FileDrc], Btk_FileAtc]

object Btk_FileAtc extends AtcFactory[java.lang.Long, Btk_FileDrc, Api[java.lang.Long, Btk_FileDrc], Btk_FileAtc] {
  override protected val sAtaClassPath: String = "ru.bitec.app.btk.Btk_FileAta$"
}

trait Btk_FileDrc extends SEntityArc[java.lang.Long] {
  def id: NLong
  def id_=(value: NLong): Unit
  def idClass: NLong
  def idClass_=(value: NLong): Unit
  def gid: NGid
  def gid_=(value: NGid): Unit
  def gidSrc: NGid
  def gidSrc_=(value: NGid): Unit
  def sFileName: NString
  def sFileName_=(value: NString): Unit
  def sExt: NString
  def sExt_=(value: NString): Unit
  def sFullFileName: NString
  def sFullFileName_=(value: NString): Unit
  def idFileStorage: NLong
  def idFileStorage_=(value: NLong): Unit
  def sSHA2: NString
  def sSHA2_=(value: NString): Unit
  def sMD5: NString
  def sMD5_=(value: NString): Unit
  def nSize: NNumber
  def nSize_=(value: NNumber): Unit
  def idAttachType: NLong
  def idAttachType_=(value: NLong): Unit
  def idSrcObjectType: NLong
  def idSrcObjectType_=(value: NLong): Unit
  def bManualMoved: NNumber
  def bManualMoved_=(value: NNumber): Unit
  def bDeleted: NNumber
  def bDeleted_=(value: NNumber): Unit
  def jParams: NString
  def jParams_=(value: NString): Unit

}

trait Btk_FileDtc[
  ARO <: Arc[java.lang.Long],
  API <: Api[java.lang.Long, ARO],
  ATA <: BaseAtaImpl[java.lang.Long, ARO, API]
] extends BaseAta[java.lang.Long, ARO, API, ATA] {

  val id: Column
  val idClass: Column
  val gid: Column
  val gidSrc: Column
  val sFileName: Column
  val sExt: Column
  val sFullFileName: Column
  val idFileStorage: Column
  val sSHA2: Column
  val sMD5: Column
  val nSize: Column
  val idAttachType: Column
  val idSrcObjectType: Column
  val bManualMoved: Column
  val bDeleted: Column
  val jParams: Column
}

Пример сгенерированного адаптера Btk_FileApa:

class Btk_FileApa extends Btk_FileApc
  with RootApa[java.lang.Long, Btk_FileDrc, Btk_FileApc]
  with JIdExtApa[EntityAbst, java.lang.Long, Btk_FileDrc]
  with AttrApa[java.lang.Long, Btk_FileDrc] {

  private type ApiType = Btk_FileApi

  private def apiInstance: ApiType = _apiInstance.asInstanceOf[ApiType]

  override def copyObject(idFrom: NLong, idTo: NLong, idParent: NLong, params: Btk_CopyObjectParam): NLong = {
    apiInstance.copyObject(idFrom, idTo, idParent, params)
  }

  override def copyObject(idFrom: NLong, idTo: NLong, idParent: NLong): NLong = {
    apiInstance.copyObject(idFrom, idTo, idParent)
  }

  override def setgidSrc(rop: ApiRop, value: NGid): Unit = {
    apiInstance.setgidSrc(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def setsFileName(rop: ApiRop, value: NString): Unit = {
    apiInstance.setsFileName(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def setsExt(rop: ApiRop, value: NString): Unit = {
    apiInstance.setsExt(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def setsFullFileName(rop: ApiRop, value: NString): Unit = {
    apiInstance.setsFullFileName(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def setidFileStorage(rop: ApiRop, value: NLong): Unit = {
    apiInstance.setidFileStorage(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def setsSHA2(rop: ApiRop, value: NString): Unit = {
    apiInstance.setsSHA2(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def setsMD5(rop: ApiRop, value: NString): Unit = {
    apiInstance.setsMD5(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def setnSize(rop: ApiRop, value: NNumber): Unit = {
    apiInstance.setnSize(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def setidAttachType(rop: ApiRop, value: NLong): Unit = {
    apiInstance.setidAttachType(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def setidSrcObjectType(rop: ApiRop, value: NLong): Unit = {
    apiInstance.setidSrcObjectType(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def setbManualMoved(rop: ApiRop, value: NNumber): Unit = {
    apiInstance.setbManualMoved(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def setbDeleted(rop: ApiRop, value: NNumber): Unit = {
    apiInstance.setbDeleted(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def setjParams(rop: ApiRop, value: NString): Unit = {
    apiInstance.setjParams(rop.asInstanceOf[ApiType#ApiRop], value)
  }

  override def parsejParams(rop: ApiRop): JESegment = {
    apiInstance.parsejParams(rop.asInstanceOf[ApiType#ApiRop])
  }

  override def getFileStorageByFile(idpFile: NLong): NString = {
    apiInstance.getFileStorageByFile(idpFile)
  }
}

Использование в прикладном коде осуществляется точно так же, как с обычным Api:

val rop = Btk_FileApc().load(123.nl)
val sFileName = rop.get(_.sFileName)
Btk_FileApc().setidAttachType(rop, Btk_AttachTypeApc().getDefault())

Результат генерации по Lib#

Пример сгенерированного контракта Btk_FileLic:

trait Btk_FileLic extends Lic {

  /** Имя переменной выборки для контекста подбора файлового хранилища */
  def uploadFile(idpFileStorage: NLong, gidpSrc: NGid, bpManualMoved: NNumber, idpAttachFileType: NLong, idpSrcObjectType: NLong, idpClassDoc: NLong, bpTemp: NNumber): NLong

  /** Имя переменной выборки для контекста подбора файлового хранилища */
  def uploadFiles(idpFileStorage: NLong, gidpSrc: NGid, bpManualMoved: NNumber, idpAttachFileType: NLong, idpSrcObjectType: NLong, idpClassDoc: NLong, bpTemp: NNumber): List[NLong]
}

object Btk_FileLic extends AviLicFactory[Btk_FileLic] {
  override protected val sLibClassPath: String = "ru.bitec.app.btk.Btk_FileLib$"
  override protected val sLiaClassPath: String = "ru.bitec.app.btk.Btk_FileLia"
}

Пример сгенерированного адаптера Btk_FileLia:

class Btk_FileLia extends Btk_FileLic {
  private type LibType = Btk_FileLib

  private def libInstance: LibType = _libInstance.asInstanceOf[LibType]

  override def uploadFile(idpFileStorage: NLong, gidpSrc: NGid, bpManualMoved: NNumber, idpAttachFileType: NLong, idpSrcObjectType: NLong, idpClassDoc: NLong, bpTemp: NNumber): NLong = {
    libInstance.uploadFile(idpFileStorage, gidpSrc, bpManualMoved, idpAttachFileType, idpSrcObjectType, idpClassDoc, bpTemp)
  }

  override def uploadFiles(idpFileStorage: NLong, gidpSrc: NGid, bpManualMoved: NNumber, idpAttachFileType: NLong, idpSrcObjectType: NLong, idpClassDoc: NLong, bpTemp: NNumber): List[NLong] = {
    libInstance.uploadFiles(idpFileStorage, gidpSrc, bpManualMoved, idpAttachFileType, idpSrcObjectType, idpClassDoc, bpTemp)
  }
}

Использование в прикладном коде осуществляется точно так же, как с обычной Lib:

Btk_FileLic().uploadFile(...)

Результат генерации по Pkg#

Пример сгенерированного контракта Btk_FilePkc:

trait Btk_FilePkc extends Pkc {

  /**
   * Проверка наличия файла в хранилище
   * @param idpFile id файла
   * @return boolean
   */
  def checkExist(idpFile: NLong): Boolean
}

object Btk_FilePkc extends PkcFactory[Btk_FilePkc] {
  override protected val sPkgClassPath: String = "ru.bitec.app.btk.Btk_FilePkg$"
  override protected val sPkaClassPath: String = "ru.bitec.app.btk.Btk_FilePka"
}

Пример сгенерированного адаптера Btk_FilePka:

class Btk_FilePka extends Btk_FilePkc {
  private type PkgType = Btk_FilePkg

  private def pkgInstance: PkgType = _pkgInstance.asInstanceOf[PkgType]

  override def checkExist(idpFile: NLong): Boolean = {
    pkgInstance.checkExist(idpFile)
  }
}

Использование в прикладном коде осуществляется точно так же, как с обычным Pkg:

new OQuery(Btk_FileAtc.Type) {
  where(t.sExt === "png".ns)
}.foreach { rop =>
  Btk_FilePkc().checkExist(rop.get(_.id))
}