1. Обзор
В этом руководстве мы узнаем, как создать простое веб-приложение с помощью Grails .
Grails (точнее, его последняя основная версия) — это фреймворк, построенный на основе проекта Spring Boot и использующий язык Apache Groovy для разработки веб-приложений.
Он вдохновлен Rails Framework для Ruby и построен на философии «конвенция над конфигурацией», которая позволяет сократить шаблонный код .
2. Настройка
Прежде всего, давайте перейдем на официальную страницу, чтобы подготовить среду. На момент написания этого руководства последней версией была 3.3.3.
Проще говоря, существует два способа установки Grails: через SDKMAN или путем загрузки дистрибутива и добавления двоичных файлов в переменную среды PATH.
Мы не будем пошагово описывать настройку, потому что она хорошо задокументирована в Grails Docs .
3. Анатомия приложения Grails
В этом разделе мы лучше поймем структуру приложения Grails. Как мы упоминали ранее, Grails предпочитает соглашение конфигурации, поэтому расположение файлов определяет их назначение. Давайте посмотрим, что у нас есть в каталоге grails-app
:
ресурсы — место, где мы храним статические файлы ресурсов, такие как стили, файлы javascript или изображения.
conf — содержит файлы конфигурации проекта:
application.yml
содержит стандартные настройки веб-приложения, такие как источник данных, типы mime и другие настройки, связанные с Grails или Spring.resources.groovy
содержит определения bean-компонентов Spring.logback.groovy
содержит конфигурацию ведения журналаконтроллеры — отвечают за обработку запросов и генерацию ответов или делегирование их представлениям. По соглашению, когда имя файла заканчивается на
*Controller
, платформа создает сопоставление URL-адресов по умолчанию для каждого действия, определенного в классе контроллера.домен — содержит бизнес-модель приложения Grails. Каждый класс, проживающий здесь, будет сопоставлен с таблицами базы данных с помощью GORM.
i18n — используется для поддержки интернационализации
init — точка входа приложения
services — здесь будет жить бизнес-логика приложения. По соглашению Grails создаст одноэлементный компонент Spring для каждой службы.
taglib — место для пользовательских библиотек тегов
представления — содержит представления и шаблоны
4. Простое веб-приложение
В этой главе мы создадим простое веб-приложение для управления студентами. Начнем с вызова команды CLI для создания скелета приложения:
grails create-app
Когда базовая структура проекта создана, давайте перейдем к реализации реальных компонентов веб-приложения.
4.1. Слой домена
Поскольку мы реализуем веб-приложение для работы со студентами, давайте начнем с создания доменного класса с именем Student
:
grails create-domain-class com.foreach.grails.Student
И, наконец, добавим к нему свойства firstName
и lastName :
class Student {
String firstName
String lastName
}
Grails применяет свои соглашения и устанавливает объектно-реляционное сопоставление для всех классов, расположенных в каталоге grails-app/domain
.
Более того, благодаря трейту GormEntity все доменные классы будут иметь доступ ко всем операциям CRUD , которые мы будем использовать в следующем разделе для реализации сервисов.
4.2. Сервисный уровень
Наше приложение будет обрабатывать следующие варианты использования:
- Просмотр списка учеников
- Создание новых студентов
- Удаление существующих студентов
Давайте реализуем эти варианты использования. Мы начнем с создания класса обслуживания:
grails create-service com.foreach.grails.Student
Давайте перейдем в каталог grails-app/services
, найдем наш только что созданный сервис в соответствующем пакете и добавим все необходимые методы:
@Transactional
class StudentService {
def get(id){
Student.get(id)
}
def list() {
Student.list()
}
def save(student){
student.save()
}
def delete(id){
Student.get(id).delete()
}
}
Обратите внимание, что службы по умолчанию не поддерживают транзакции . Мы можем включить эту функцию, добавив в класс аннотацию @Transactional .
4.3. Уровень контроллера
Чтобы сделать бизнес-логику доступной для пользовательского интерфейса, давайте создадим StudentController
, вызвав следующую команду:
grails create-controller com.foreach.grails.Student
По умолчанию Grails внедряет bean-компоненты по именам . Это означает, что мы можем легко внедрить экземпляр Singleton StudentService
в наш контроллер, объявив переменную экземпляра с именем studentService
.
Теперь мы можем определить действия для чтения, создания и удаления учеников.
class StudentController {
def studentService
def index() {
respond studentService.list()
}
def show(Long id) {
respond studentService.get(id)
}
def create() {
respond new Student(params)
}
def save(Student student) {
studentService.save(student)
redirect action:"index", method:"GET"
}
def delete(Long id) {
studentService.delete(id)
redirect action:"index", method:"GET"
}
}
По соглашению действие index()
этого контроллера будет сопоставлено с URI /student/index ,
действие show()
с /student/show
и так далее.
4.4. Просмотр слоя
Настроив действия нашего контроллера, мы можем теперь приступить к созданию представлений пользовательского интерфейса. Мы создадим три страницы Groovy Server для перечисления, создания и удаления студентов.
По соглашению Grails отображает представление на основе имени и действия контроллера. Например, действие index()
из StudentController
будет преобразовано в /grails-app/views/student/index.gsp.
**
``**
Начнем с реализации представления /grails-app/views/student/index.gsp
,
которое будет отображать список учащихся. Мы будем использовать тег <f:table/>
для создания HTML-таблицы, отображающей всех учащихся, возвращенных действием index()
в нашем контроллере.
``
По соглашению, когда мы отвечаем списком объектов, Grails добавит суффикс «List» к имени модели , чтобы мы могли получить доступ к списку объектов student с помощью переменной studentList
:
<!DOCTYPE html>
<html>
<head>
<meta name="layout" content="main" />
</head>
<body>
<div class="nav" role="navigation">
<ul>
<li><g:link class="create" action="create">Create</g:link></li>
</ul>
</div>
<div id="list-student" class="content scaffold-list" role="main">
<f:table collection="${studentList}"
properties="['firstName', 'lastName']" />
</div>
</body>
</html>
Теперь перейдем к представлению /grails-app/views/student/create.gsp
,
которое
позволяет пользователю создавать новых учащихся. Мы будем использовать встроенный тег <f:all/>
, который отображает форму для всех свойств данного компонента:
``
<!DOCTYPE html>
<html>
<head>
<meta name="layout" content="main" />
</head>
<body>
<div id="create-student" class="content scaffold-create" role="main">
<g:form resource="${this.student}" method="POST">
<fieldset class="form">
<f:all bean="student"/>
</fieldset>
<fieldset class="buttons">
<g:submitButton name="create" class="save" value="Create" />
</fieldset>
</g:form>
</div>
</body>
</html>
Наконец, давайте создадим представление /grails-app/views/student/show.gsp для
просмотра
и последующего удаления учащихся .
Среди других тегов мы воспользуемся <f:display/>
, который принимает bean-компонент в качестве аргумента и отображает все его поля:
<!DOCTYPE html>
<html>
<head>
<meta name="layout" content="main" />
</head>
<body>
<div class="nav" role="navigation">
<ul>
<li><g:link class="list" action="index">Students list</g:link></li>
</ul>
</div>
<div id="show-student" class="content scaffold-show" role="main">
<f:display bean="student" />
<g:form resource="${this.student}" method="DELETE">
<fieldset class="buttons">
<input class="delete" type="submit" value="delete" />
</fieldset>
</g:form>
</div>
</body>
</html>
4.5. Модульные тесты
Grails в основном использует Spock для целей тестирования. Если вы не знакомы со Spock, мы настоятельно рекомендуем сначала прочитать это руководство .
Давайте начнем с модульного тестирования действия index()
нашего StudentController.
Мы создадим макет метода list()
из StudentService
и проверим, возвращает ли index()
ожидаемую модель:
void "Test the index action returns the correct model"() {
given:
controller.studentService = Mock(StudentService) {
list() >> [new Student(firstName: 'John',lastName: 'Doe')]
}
when:"The index action is executed"
controller.index()
then:"The model is correct"
model.studentList.size() == 1
model.studentList[0].firstName == 'John'
model.studentList[0].lastName == 'Doe'
}
Теперь давайте проверим действие delete()
. Мы проверим, был ли вызван метод delete()
из StudentService
, и проверим перенаправление на индексную страницу:
void "Test the delete action with an instance"() {
given:
controller.studentService = Mock(StudentService) {
1 * delete(2)
}
when:"The domain instance is passed to the delete action"
request.contentType = FORM_CONTENT_TYPE
request.method = 'DELETE'
controller.delete(2)
then:"The user is redirected to index"
response.redirectedUrl == '/student/index'
}
4.6. Интеграционные тесты
Далее давайте посмотрим, как создавать интеграционные тесты для сервисного уровня. В основном мы будем тестировать интеграцию с базой данных, настроенной в файле grails-app/conf/application.yml.
По умолчанию Grails использует для этой цели базу данных H2 в памяти.
Прежде всего, давайте начнем с определения вспомогательного метода для создания данных для заполнения базы данных:
private Long setupData() {
new Student(firstName: 'John',lastName: 'Doe')
.save(flush: true, failOnError: true)
new Student(firstName: 'Max',lastName: 'Foo')
.save(flush: true, failOnError: true)
Student student = new Student(firstName: 'Alex',lastName: 'Bar')
.save(flush: true, failOnError: true)
student.id
}
Благодаря аннотации @Rollback
в нашем классе интеграционного теста каждый метод будет выполняться в отдельной транзакции, которая будет откатываться в конце теста .
Посмотрите, как мы реализовали интеграционный тест для нашего метода list() :
void "test list"() {
setupData()
when:
List<Student> studentList = studentService.list()
then:
studentList.size() == 3
studentList[0].lastName == 'Doe'
studentList[1].lastName == 'Foo'
studentList[2].lastName == 'Bar'
}
Кроме того, давайте протестируем метод delete()
и проверим, уменьшилось ли общее количество студентов на единицу:
void "test delete"() {
Long id = setupData()
expect:
studentService.list().size() == 3
when:
studentService.delete(id)
sessionFactory.currentSession.flush()
then:
studentService.list().size() == 2
}
5. Запуск и развертывание
Запуск и развертывание приложений можно выполнить, вызвав одну команду через интерфейс командной строки Grails.
Для запуска приложения используйте:
grails run-app
По умолчанию Grails устанавливает Tomcat на порт 8080.
Давайте перейдем по адресу http://localhost:8080/student/index
, чтобы увидеть, как выглядит наше веб-приложение:
Если вы хотите развернуть свое приложение в контейнере сервлетов, используйте:
grails war
для создания готового к развертыванию военного артефакта.
6. Заключение
В этой статье мы сосредоточились на том, как создать веб-приложение Grails, используя философию соглашения вместо конфигурации. Мы также увидели, как выполнять модульные и интеграционные тесты с помощью инфраструктуры Spock.
Как всегда, весь используемый здесь код можно найти на GitHub .