Vaadin 프레임워크 소개: 간단한 게시판 프로젝트(2)
1. 들어가며
웹 애플리케이션 개발에서 프론트엔드와 백엔드를 통합하는 방식은 개발자에게 중요한 선택 중 하나입니다. Java 생태계에서는 Spring Boot와 같은 백엔드 프레임워크가 널리 사용되고, 프론트엔드에서는 React, Angular, Vue.js와 같은 자바스크립트 프레임워크가 주류를 이루고 있습니다.
하지만 Vaadin을 사용하면 Java만으로 프론트엔드와 백엔드를 모두 개발할 수 있습니다. 이 글에서는 Vaadin의 특징을 소개하고, 간단한 게시판 애플리케이션 예제를 통해 실제로 Vaadin이 어떻게 동작하는지를 보여드리겠습니다.
2. Vaadin이란 무엇인가?
Vaadin은 Java 기반의 풀스택 웹 프레임워크로, 백엔드와 프론트엔드 개발을 통합할 수 있는 강력한 도구입니다. 주요 특징은 다음과 같습니다:
- 서버 사이드 렌더링: UI를 서버에서 처리하고, 브라우저에 HTML을 전송합니다.
- 컴포넌트 기반 개발: HTML과 자바스크립트를 직접 작성하지 않고, Java 코드로 UI 컴포넌트를 생성할 수 있습니다.
- Spring Boot 통합 지원: Vaadin은 Spring Boot와 자연스럽게 통합되어, 기존 Java 개발 환경에서 쉽게 사용할 수 있습니다.
이전 글에서 더 자세한 특징을 다루었습니다. ( [Web/Spring] - Vaadin 프레임워크 소개: hawkBit 사례 분석(1))
3. 예제 애플리케이션 소개
우리는 간단한 게시판 애플리케이션을 Vaadin으로 구현할 것입니다. 이 애플리케이션은 다음과 같은 기능을 제공합니다:
- 게시글 목록 보기 (ListView)
- 게시글 상세 보기 (DetailView)
4. 프로젝트 구성
- Post.java: 게시글 데이터를 저장하는 클래스
- PostService.java: 게시글 데이터를 관리하는 서비스 클래스
- ListView.java: 게시글 목록을 보여주는 뷰
- DetailView.java: 특정 게시글의 상세 내용을 보여주는 뷰
- styles.css: 애플리케이션의 스타일을 정의하는 CSS 파일
- posts.json: 게시글 데이터를 담은 JSON 파일
project-root/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/
│ │ │ ├── Post.java
│ │ │ ├── PostService.java
│ │ │ ├── ListView.java
│ │ │ └── DetailView.java
│ │ └── resources/
│ │ └── posts.json
├── frontend/
│ └── styles.css
└── pom.xml
프로젝트 설정 (Gradle - Grovvy)
아래는 Gradle을 사용하여 Vaadin 프로젝트를 설정하는 예시입니다:
plugins {
id 'java'
id 'org.springframework.boot' version '3.4.1'
id 'io.spring.dependency-management' version '1.1.7'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
ext {
set('vaadinVersion', "24.1.0")
}
dependencies {
implementation "com.vaadin:vaadin-spring-boot-starter"
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
dependencyManagement {
imports {
mavenBom "com.vaadin:vaadin-bom:${vaadinVersion}"
}
}
tasks.named('test') {
useJUnitPlatform()
}
5. 주요 코드
5.1 Post.java
게시글 데이터를 나타내는 간단한 POJO 클래스입니다.
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Post {
private int id;
private String title;
private String content;
}
5.2 PostService.java
PostService
는 JSON 파일에서 게시글 데이터를 로드하고, 이를 관리하는 역할을 합니다. 주요 메서드로는 getAllPosts()
와 getPostById(int id)
가 있습니다. getAllPosts()
는 모든 게시글을 반환하며, getPostById(int id)
는 특정 ID에 해당하는 게시글을 반환합니다.
@Service
public class PostService {
private List<Post> posts;
@PostConstruct
public void init(){
loadPosts();
}
private void loadPosts() {
try (InputStream input = getClass().getResourceAsStream("/posts.json");
BufferedInputStream bufferedInput = new BufferedInputStream(input)) {
ObjectMapper mapper = new ObjectMapper();
posts = mapper.readValue(bufferedInput, new TypeReference<>() {});
} catch (Exception e) {
e.printStackTrace();
}
}
public List<Post> getAllPosts() {
return posts;
}
public Optional<Post> getPostById(int id) {
return posts.stream().filter(post -> post.getId() == id).findFirst();
}
}
5.3 ListView.java
ListView
는 게시글 목록을 표시하는 화면입니다. 각 게시글에 대해 제목과 내용 일부를 보여주며, "View Details" 버튼을 통해 상세 페이지로 이동할 수 있습니다.
@Route("list")
@CssImport("./styles.css")
public class ListView extends VerticalLayout {
private final PostService postService;
@Autowired
public ListView(PostService postService){
this.postService = postService;
initView();
}
private void initView() {
List<Post> posts = postService.getAllPosts();
posts.forEach(this::initListItem);
}
private void initListItem(Post post) {
Div card = new Div();
card.addClassName("card");
Div contentContainer = new Div();
contentContainer.addClassName("content-container");
Div title = new Div();
title.setText(post.getTitle());
title.addClassName("card-title");
Div contentSnippet = new Div();
contentSnippet.setText(post.getContent().substring(0, Math.min(50, post.getContent().length())) + "...");
contentSnippet.addClassName("card-content");
Button detailButton = new Button("View Details", event ->
getUI().ifPresent(ui -> ui.navigate("detail/" + post.getId()))
);
detailButton.addClassName("view-details-button");
contentContainer.add(title, contentSnippet);
card.add(contentContainer, detailButton);
add(card);
}
}
실행된 프로젝트의 화면:
5.4 DetailView.java
DetailView
는 특정 게시글의 상세 내용을 보여주는 화면입니다. URL에 포함된 게시글 ID를 통해 해당 게시글을 조회합니다.
@Route("detail")
@CssImport("./styles.css")
public class DetailView extends VerticalLayout implements HasUrlParameter<Integer> {
private final PostService postService;
@Autowired
public DetailView(PostService postService) {
this.postService = postService;
setPadding(true);
setSpacing(true);
setSizeFull();
}
@Override
public void setParameter(BeforeEvent event, Integer postId) {
Optional<Post> postOptional = postService.getPostById(postId);
if (postOptional.isPresent()) {
initPresentView(postOptional.get());
} else {
initNotPresentView();
}
}
void initPresentView(Post post) {
Div title = new Div();
title.setText(post.getTitle());
title.addClassName("detail-title");
Div content = new Div();
content.setText(post.getContent());
content.addClassName("detail-content");
Button backButton = new Button("Back to List", e ->
getUI().ifPresent(ui -> ui.navigate("list"))
);
backButton.addClassName("back-button");
add(title, content, backButton);
}
void initNotPresentView() {
Div errorMessage = new Div();
errorMessage.setText("Post not found.");
errorMessage.getStyle().set("color", "red").set("font-size", "18px");
Button backButton = new Button("Back to List", e ->
getUI().ifPresent(ui -> ui.navigate("list"))
);
add(errorMessage, backButton);
}
}
실행된 프로젝트의 화면:
6. 스타일 정의
styles.css
파일을 통해 카드 스타일, 버튼 스타일 등을 정의했습니다. 여기서 주의할 점은 @CssImport("./styles.css")
을 사용하려면 프로젝트 루트 폴더 내에 frontend
폴더가 존재하고 그 안에 styles.css
파일이 있어야 합니다. 예를 들어, 폴더 구조는 다음과 같습니다:
project-root/
├── src/
├── frontend/
│ └── styles.css
└── pom.xml
이러한 폴더 구조를 갖추어야 Vaadin이 CSS 파일을 올바르게 로드할 수 있습니다. 만약 frontend
폴더가 없거나 경로가 잘못되었을 경우, 프로젝트 실행 시 아래와 같은 오류가 발생할 수 있습니다:
Failed to find the following css files in the `node_modules` or `/path/to/your/project/frontend` directory tree:
- ./styles.css
Check that they exist or are installed. If you use a custom directory for your resource files instead of the default `frontend` folder then make sure it's correctly configured (e.g. set 'vaadin.frontend.frontend.folder' property)
이러한 오류를 방지하려면 frontend
폴더 안에 CSS 파일을 올바르게 배치하고, 필요한 경우 application.properties
파일에서 vaadin.frontend.frontend.folder
속성을 설정하여 맞춤 경로를 지정할 수 있습니다.
7. 마치며
프로젝트 실행하기
프로젝트를 빌드하고 특정 경로(예: /list)에 접근하면 Vaadin이 자동으로 생성한 HTML 및 JavaScript 파일이 브라우저에 전달됩니다. 이 파일들은 프로젝트 내부의 특정 경로에서 확인할수 있습니다. (ex. /frontend)
이러한 파일은 Vaadin의 컴포넌트 기반 UI를 구성하는 데 필요한 리소스들로, 프로젝트 내부에서 동적으로 생성됩니다. 대표적인 파일로는 index.html, frontend-es5.js, frontend-es6.js 등이 있으며, 각각 다음과 같은 내용을 담고 있습니다:
- index.html: 애플리케이션의 기본 HTML 구조를 정의하며, Vaadin이 생성한 컴포넌트를 로드하는 역할을 합니다.
- frontend-es5.js, frontend-es6.js: Vaadin UI 컴포넌트와 관련된 JavaScript 코드가 포함되어 있으며, 브라우저 호환성을 위해 ES5 및 ES6 버전으로 제공됩니다.
Vaadin 추가 자료
Vaadin은 Java 기반 풀스택 개발 환경을 제공하며, 프론트엔드와 백엔드의 경계를 허물어 개발 생산성을 크게 향상시킬 수 있습니다. 특히, 기존 Spring Boot 애플리케이션과의 통합이 용이하여 엔터프라이즈 환경에서도 실무에 쉽게 적용할 수 있습니다. 예를 들어, 관리자 대시보드, 데이터 입력 애플리케이션, IoT 모니터링 시스템과 같은 내부 도구 개발에 매우 적합합니다. 더 나아가 Vaadin 공식 문서(https://vaadin.com/docs), 커뮤니티 포럼, GitHub 저장소 등을 활용하여 추가적인 학습 리소스를 탐색해 보세요. 이를 통해 더욱 깊이 있는 Vaadin 활용 방법을 익힐 수 있습니다.