Статьи

Grails Учебник для начинающих — Grails Service Layer

В этом руководстве обсуждается важность уровня обслуживания в Grails и как с ним работать. Также объясняется, как управлять транзакциями и как их использовать.

Вступление

Разделение проблем

Рассмотрим эту аналогию: представьте себе компанию, в которой сотрудникам назначаются задания различной природы. Например, скажем, есть сотрудник по имени Джон Доу со следующими обязанностями:

  • Вести учет и выпуск чековых платежей
  • Принимайте звонки от клиентов для поддержки продукта
  • Выполнять административные задачи, такие как бронирование авиабилетов и размещение руководителей.
  • Управление графиком генерального директора

Как вы могли видеть, работа Джона слишком сложна, потому что ему нужно многозадачность для совершенно разных типов задач. Ему нужно изменить свое настроение при переключении с одного задания на другое. Он с большей вероятностью будет подвержен стрессу и совершит много ошибок. Его ошибки могут стоить много денег в долгосрочной перспективе. Другая проблема заключается в том, что позже Джона заменить нелегко, так как он слишком занят сложной задачей.

Аналогично в разработке программного обеспечения, это не очень хорошая идея написать класс, который имеет различный характер обязанностей. Общее согласие экспертов заключается в том, что один класс или исходный файл должен быть задействован только в одной природе задачи. Это называется « разделение интересов ». Если что-то происходит, это приведет к появлению ошибок и проблем позже, так как приложение будет очень сложно поддерживать. Хотя эту концепцию так просто сформулировать, эффект от проекта огромен!

Рассмотрим контроллер в Grails. Внутри контроллера мы можем сделать ff:

  • Обрабатывать логику маршрутизации
  • Вызывать операции GORM для манипулирования данными в базе данных
  • Визуализируйте текст и покажите его пользователю.

Однако не рекомендуется делать все эти вещи внутри контроллера. Grails позволяет разработчику делать все это вместе для гибкости, но этого следует избегать. Настоящая цель контроллера — иметь дело с логикой маршрутизации, что означает:

  1. Получать запросы от пользователей
  2. Вызвать наиболее подходящую бизнес-логику
  3. Вызовите представление для отображения результата

Логика представления должна позаботиться о файлах Groovy Server Pages (GSP). Прочтите этот предыдущий учебник о GSP, если вы не знакомы с ним.

Для бизнес-логики они должны быть реализованы внутри сервисного уровня. У Grails есть поддержка и обработка по умолчанию для сервисного уровня.

Не повторяй принцип (СУХОЙ)

Еще одно преимущество использования сервисного уровня — вы можете повторно использовать бизнес-логику в нескольких местах без дублирования кода. Наличие единственной копии конкретной бизнес-логики сделает проект короче (с точки зрения строк кода) и проще в обслуживании. Изменение бизнес-логики потребует изменения только в одном конкретном месте.

Отсутствие дублирования кода является частью другого передового опыта, который называется « Не повторяй себя (СУХОЙ)».

Создать сервис

Чтобы создать класс обслуживания, вызовите команду create-service из корневой папки вашего проекта. Например, используйте следующую команду внутри командной строки или терминала:

1
grails create-service asia.grails.sample.Test

Вы также можете создать класс обслуживания в IDE GGTS .

Просто щелкните правой кнопкой мыши проект, выберите New, а затем Service.

zz01

Укажите имя и нажмите «Готово»:

zz02

Ниже создан созданный класс. В качестве примера приведен метод по умолчанию. Здесь мы будем писать бизнес-логику.

1
2
3
4
5
package asia.grails.sample
class TestService {
    def serviceMethod() {
    }
}

Просто добавьте столько функций, сколько необходимо для бизнес-логики и операций GORM. Вот пример:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
package asia.grails.sample
class StudentService {
  Student createStudent(String lastName, String firstName) {
    Student student = new Student()
    student.lastName = lastName
    student.firstName = firstName
    student.referenceNumber = generateReferenceNumber()
    student.save()
    return student
  }
   private String generateReferenceNumber() {
    // some specific logic for generating reference number
    return referenceNumber
  }
}

Внедрение услуги

Сервис автоматически внедряется внутри контроллера, просто определяя переменную с правильным именем. (используйте переменную studentService для добавления экземпляра StudentService). Пример:

01
02
03
04
05
06
07
08
09
10
package asia.grails.sample
class MyController {
  def studentService
   def displayForm() {
  }
   def handleFormSubmit() {
    def student = studentService.createStudent(params.lastName, params.firstName)
    [student:student]
  }
}

Если вы не знакомы с Spring и концепцией впрыска, это означает, что вам не нужно делать ничего особенного. Просто объявите переменную studentService, и среда Grails автоматически назначит ей экземпляр. Просто заявите и используйте прямо сейчас.

Вы также можете внедрить услугу в другой сервис. Например:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
package asia.grails.sample
class PersonService {
  Person createPerson(String lastName, String firstName) {
    Person p = new Person()
    p.lastName = lastName
    p.firstName = firstName
    p.save()
    return p
  }
}
 package asia.grails.sample
class EmployeeService {
  def personService
  Employee createEmployee(String lastName, String firstName) {
    Person person = personService.createPerson(lastName, firstName)
    Employee emp = new Employee()
    emp.person = person
    emp.employmentDate = new Date()
    emp.save()
    return emp
  }
}

Вы также можете внедрить сервис внутри Bootstrap.Grovy. Например:

1
2
3
4
5
6
7
8
9
class BootStrap {
  def studentService
  def init = { servletContext ->
    if ( Student.count() == 0 ) { // if no students in the database, create some test data
      studentService.createStudent("Doe", "John")
      studentService.createStudent("Smith", "Jame")
    }
  }
}

Вы также можете добавить сервис в библиотеку тегов. Например:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
package asia.grails.sample
class StudentService {
  def listStudents() {
    return Student.list()
  }
}
 class MyTagLib {
  StudentService studentService
  static namespace = "my"
   def renderStudents = {
    def students = studentService.listStudents()
    students.each { student ->
      out << "<div>Hi ${student.firstName} ${student.lastName}, welcome!</div>"
    }
  }
}

Управление транзакциями

Если вы новичок в работе с базами данных, транзакция является очень важной концепцией. Обычно мы хотим, чтобы определенная последовательность изменений базы данных была успешной. Если это невозможно, мы не хотим, чтобы операции вообще происходили. Например, рассмотрим этот код для перевода средств между двумя банковскими счетами:

1
2
3
4
5
6
7
8
class AccountService {
  def transferFunds(long accountFromID, long accountToID, long amount) {
    Account accountFrom = Account.get(accountFromID)
    Account accountTo = Account.get(accountToID)
    accountFrom.balance = accountFrom.balance - amount
    accountTo.balance = accountTo.balance + amount
  }
}

Этот код вычитает деньги с одного счета ( accountFrom.balance = accountFrom.balance — сумма ) и добавляет деньги на другой счет ( accountTo.balance = accountTo.balance + сумма ). Представьте, что что-то произошло (Исключение) после вычета из исходной учетной записи и целевой учетной записи, которая не была обновлена. Деньги будут потеряны и не будут учтены. Для кодов этого типа нам нужно поведение «все или ничего». Эта концепция также называется атомарностью .

Для сценария, приведенного выше, управление транзакциями требуется для достижения желаемого поведения. Программа начнет разговор с базой данных, где любые операции обновления просто записываются во временное пространство (например, на бумаге). В дальнейшем программе потребуется сообщить базе данных, хочет ли она внести изменения окончательно, или вычеркнуть все, что было сделано ранее.

Поскольку Grails поддерживает транзакции, он автоматически делает с нами следующие вещи, когда мы объявляем сервис транзакционным:

  • Если все операции с БД успешны, отразите изменения в базе данных (это также называется фиксацией )
  • Если одна операция дБ приводит к исключению, вернитесь в исходное состояние и забудьте / отмените все предыдущие операции (это также называется откатом )

Декларация о сделке

Grails поддерживает управление транзакциями внутри сервисов. По умолчанию все сервисы являются транзакционными. Таким образом, эти 3 объявления имеют одинаковый эффект.

1
2
class CountryService {
}
1
2
3
class CountryService {
    static transactional = true
}
1
2
3
@Transactional
class CountryService {
}

Для удобства чтения я предлагаю объявлять статические транзакции вверху каждого сервиса.

Обратите внимание, что не все приложения требуют транзакции. Не чувствительные программы, такие как программное обеспечение для блогов, могут выжить без него. Транзакции обычно требуются при работе с конфиденциальными данными, такими как финансовая информация.

Использование транзакций вводит накладные расходы. Вот пример того, как отключить его в службе:

1
2
3
class CountryService {
    static transactional = false
}

Как заставить откат

Одна из самых важных вещей, которую нужно запомнить, — какой код писать, чтобы заставить Grails откатить текущую последовательность операций. Чтобы сделать это, просто вызовите RuntimeException или его потомка. Например, при этом откроется операция accountFrom.balance = accountFrom.balance — сумма

1
2
3
4
5
6
7
8
9
class AccountService {
  def transferFunds(long accountFromID, long accountToID, long amount) {
    Account accountFrom = Account.get(accountFromID)
    Account accountTo = Account.get(accountToID)
    accountFrom.balance = accountFrom.balance - amount
    throw new RuntimeException("testing only")
    accountTo.balance = accountTo.balance + amount
  }
}

Но этот код не будет:

1
2
3
4
5
6
7
8
9
class AccountService {
  def transferFunds(long accountFromID, long accountToID, long amount) {
    Account accountFrom = Account.get(accountFromID)
    Account accountTo = Account.get(accountToID)
    accountFrom.balance = accountFrom.balance - amount
    throw new Exception("testing only")
    accountTo.balance = accountTo.balance + amount
  }
}

Значение строки с вычитанием будет зафиксировано, но строка с добавлением не будет достигнута.

замечания

Это еще не полное обсуждение уровня обслуживания Grails. Обратитесь к официальной документации по адресу http://grails.org/doc/latest/guide/services.html или прочитайте будущие сообщения в этом блоге.

Ссылка: Учебник Grails для начинающих — сервисный уровень Grails от нашего партнера JCG Джонатана Тана в блоге Grails .