엔지니어링

React와 Elastic App Search를 사용하여 애플리케이션 검색을 구축하는 방법

사용자가 어떤 것을 검색할 때는 정확한 결과를 받고 싶어합니다. 그러나 정확한 결과는 방문자가 다시 찾아오도록 만들 만한 요소의 일부일 뿐입니다. 검색 경험 자체도 기분이 좋아야 합니다. 검색은 빠르고, 입력에 바로 반응하며, 지능적이고 효과적이라는 느낌이 들어야 합니다.

이 튜토리얼에서는 React와 Elastic App Search 자바스크립트 클라이언트를 사용하여 유동적이고 강력한 검색 경험을 구축하는 방법을 보여드리면서 애플리케이션 검색에 대해 가르쳐 드립니다. 결국에는 보기 좋고 정확한 React화된 애플리케이션을 갖추시게 되며, 이 애플리케이션을 사용하여 URI의 일부로 유지되는 상태와 패싯별로 정렬된 다양한 npm 패키지를 실시간으로 검색하실 수 있습니다.

완성된 코드는 GitHub에서 확인하실 수 있습니다.


안녕하세요!

Elastic의 오픈 소스 라이브러리인 Search UI를 사용하여 훨씬 더 빠르게 훌륭한 검색을 구축하는 데 대한 최신 글이 게시되었습니다.

이 글을 대신 읽어보세요!


요건

계속하려면 다음이 필요합니다...

  1. Node.js의 최신 버전.
  2. npm의 최신 버전.
  3. Elastic App Search 서비스 계정 또는 활성 14일 무료 체험판.
  4. 약 30분 정도.

App Search, 소개

애플리케이션은 데이터를 중심으로 구축됩니다. Facebook은 '친구' 데이터를 재미있는 방식으로 제공하여 소셜 서클에 폭발적으로 퍼졌습니다. eBay는 중고 물품을 찾고 구매하는 가장 간소화된 방법으로 시작되었습니다. Wikipedia는 독자들이 쉽게 배울 수 있도록 만들었습니다. 모든 것에 대해서 말이죠!

애플리케이션은 데이터 문제를 해결하기 위해 존재합니다. 이러한 노력에서, 검색은 필수적인 동반자입니다. 주요 애플리케이션의 경우, 친구, 제품, 대화 또는 기사를 찾는 등의 검색 기능이 핵심적인 부분을 차지하게 됩니다. 데이터 세트가 더 크고 흥미로울수록, 애플리케이션이 더 인기를 끌게 됩니다. 특히, 검색이 정확하고 보람이 있는 경우에는 더욱 그렇습니다.

Elastic App Search는 분산형 오픈 소스 RESTful 검색 엔진인 Elasticsearch 위에 구축됩니다. Elastic App Search를 사용하여, 개발자는 프리미엄 애플리케이션 검색 사용 사례를 처리하도록 최적화된 강력한 API 엔드포인트 세트에 액세스할 수 있습니다.

엔진 시작하기

시작하려면, App Search 내에 엔진을 만듭니다.

엔진은 색인을 위해 개체를 수집합니다. 개체는 데이터입니다. 즉, 친구 프로필이나 제품 또는 위키 페이지입니다. 데이터가 App Search에 수집되면, 유연한 스키마에 기반하여 색인되고 검색에 최적화됩니다. 여기에서, 다양한 클라이언트 라이브러리를 활용하여 즐거운 검색 경험을 만들 수 있습니다.

이 예에서는, 우리의 엔진을 node-modules라고 부르겠습니다.

엔진이 생성되면, Credentials(자격 증명) 페이지에서 다음 세 가지가 필요하게 됩니다.

  1. host-라는 접두사가 붙은 호스트 식별자
  2. private-이라는 접두사가 붙은 프라이빗 API 키
  3. private-라는 접두사가 붙은 퍼블릭 검색 키

그러면 프로젝트를 복제하고, 디렉터리에 들어가, 스타터 브랜치를 체크아웃한 다음, npm 설치를 실행할 수 있습니다.

$ git clone https://github.com/swiftype/app-search-demo-react.git
$ cd react-tutorial && git checkout starter && npm install

좋습니다. 애플리케이션이 준비되었습니다. 하지만 검색하려면 데이터가 필요합니다...

수집 ~

대부분의 경우, 개체는 데이터베이스 또는 백엔드 API 내에 존재합니다. 이러한 예를 들어, 정적 .json 파일을 사용하겠습니다. 리포지토리에는 init-data.js와 index-data.js, 이렇게 두 개의 스크립트가 포함되어 있습니다. 전자는 npm에서 잘 포맷된 node-module 데이터를 가져오는 데 사용된 스크래퍼입니다. 데이터는 node-modules.json 파일 내에 존재합니다. 후자는 색인을 위해 해당 데이터를 App Search 엔진으로 수집하게 되는 인덱서입니다.

인덱서 스크립트를 실행하려면, 호스트 식별자프라이빗 API 키를 함께 전달해야 합니다.

$ REACT_APP_HOST_IDENTIFIER={Your Host Identifier} \
REACT_APP_API_KEY={Your Private API Key} \
npm run index-data

개체는 100개의 배치로 App Search 엔진에 빠르게 전송되고 인덱스가 구성됩니다.

이제 문서로 색인된 ~9,500 npm 패키지가 있는 새로 생성된 엔진을 위한 대시보드가 있어야 합니다. 그 내용에 익숙해지도록 데이터를 자세히 살펴보는 것이 유용할 수 있습니다.

app_search_engine_overview.png

반응성

엔진을 가득 채우고 준비가 완료되면, 핵심 애플리케이션 구축을 시작할 수 있습니다.

$ npm start

프로젝트 디렉터리 내에서 npm을 시작하면 React 보일러 플레이트가 열립니다. App.css에서 가져오는 스타일링을 통해, 필요에 맞게 사용자 정의할 수 있습니다.

가까운 미래에, 우리의 검색 쿼리를 입력할 수 있는 검색 상자가 필요할 것입니다. 사용자들은 이 유용한 직사각형의 검색 상자를 찾을 것입니다. 검색 엔진과 브라우저가 사용자들을 이렇게 잘 훈련시켜 놓았기 때문입니다. 여기에 입력하시고, 원하는 것을 찾으세요!

//App.css
...
.App-search-box {
height: 40px;
width: 500px;
font-size: 1em;
margin-top: 10px;
}

또한 App Search을 위한 액세스 자격 증명을 .env 파일과 같은 안전한 장소에 저장해 두어야 합니다.

프로젝트 루트 디렉터리 내에 하나를 생성하고 다음과 같이 입력합니다.

//.env
REACT_APP_HOST_IDENTIFIER={your host identifier, prefixed with host-}
REACT_APP_SEARCH_KEY={your public search key, prefixed with search-}

변수를 안전하게 숨겨둔 상태에서, 검색 로직 작성을 시작할 수 있습니다.

검색 시작하기

App.js 파일은 핵심 로직이 저장되는 곳입니다. 이 파일은 대부분의 다른 스타터 파일과 함께 구성 없이 부트스트랩 React 애플리케이션을 지원하는 도구인 create-react-app에 의해 생성되었습니다. 검색을 테스트하기 위한 일부 로직을 작성하기 전에, Swiftype App Search JavaScript 클라이언트 라이브러리를 설치해야 합니다.

$ npm install --save swiftype-app-search-javascript

App.js에 다음 코드를 입력합니다. 그러면 기본 검색을 수행하게 됩니다.

우리는 다음과 같이 예시 검색어로 foo를 하드코딩하겠습니다.

import * as SwiftypeAppSearch from "swiftype-app-search-javascript";
const client = SwiftypeAppSearch.createClient({
hostIdentifier: process.env.REACT_APP_HOST_IDENTIFIER,
apiKey: process.env.REACT_APP_SEARCH_KEY,
engineName: "node-modules"
});
//우리는 무엇이든 쿼리할 수 있습니다 -- foo는 예시입니다.
const query = "foo";
const options = {};
client.search(query, options)
.then(resultList => console.log(resultList))
.catch(error => console.log(error))

브라우저는 console.log를 통해 resultList 배열을 새로고침하고 생성하게 됩니다. 배열을 탐색하기 위해, 브라우저의 개발자 콘솔을 열 수 있습니다. foo 쿼리를 다른 스트링으로 대체하여 몇 개의 쿼리를 더 시도할 수 있습니다. 쿼리가 변경되고 페이지가 새로고침되면, 결과 집합이 어떻게 맞춰서 조정되었는지 볼 수 있습니다.

됐습니다. 이로써, 우리는 이미 node-modules를 통해 검색을 하고 있습니다.

최종적인 결과의 적합성

우리는 검색하기 위해 간단한 패턴을 가지고 있지만, 결과는 console.log에 숨겨진 채로 거의 쓸모가 없습니다. 우리는 기본 React 스타일과 이전 코드를 제거한 다음, 확장할 것입니다.

우리는 다음을 생성하려고 합니다...

  1. 응답 속성을 보유하게 될 상태 변수.
  2. client.search를 사용하여 App Search를 쿼리하게 될 performQuery 메서드. 이는 쿼리 결과를 응답 속성 내에 저장하게 됩니다.
  3. 애플리케이션 로드 시 한 번 실행될 componentDidMount 라이프사이클 훅. 우리는 foo를 다시 쿼리할 것이지만, 우리가 원하는 어떤 쿼리든 사용할 수 있습니다.
  4. 결과적인 데이터 출력과 총 결과 수를 보유하기 위한 구조적 HTML.
//App.js
// ... 끝이 잘림!
class App extends Component {
state = {
// 최신 쿼리 응답을 보유하는 새 상태 속성
response: null
};
componentDidMount() {
/*구성 요소 DidMount에서 이 값을 호출하면
앱이 처음 로드될 때 화면에 결과가 표시*/
this.performQuery("foo");
}
// 쿼리를 수행하고 응답을 저장하는 방법
performQuery = queryString => {
client.search(queryString, {}).then(
response => {
// 전체 응답을 검사할 수 있도록 지금 이 항목을 추가
console.log(response);
this.setState({ response });
},
error => {
console.log(`error: ${error}`);
}
);
};
render() {
const {response} = this.state;
if (!response) return null;
return (


Node Module Search



{/*이 쿼리의 총 결과 수 표시*/}

{response.info.meta.page.total_results} Results


{/*결과를 반복하고 이름과 설명을 표시*/}
{response.results.map(result => (

Name: {result.getRaw("name")}


Description: {result.getRaw("description")}



))}

);
}
}
// ... 끝이 잘림!

저장을 누르는 순간, http://localhost:3000 내에 결과가 나타나게 됩니다. 27개의 결과와 일부 훌륭한 사운딩 모듈이 말입니다. 무언가 잘못되면, 코드 내에 두 개의 two console.log가 중첩되어 있으므로 콘솔을 체크아웃할 수 있습니다.

팬시 복싱

우리는 foo를 우리의 쿼리를 하드코딩했습니다. 검색을 가장 가치 있게 만드는 것은 자유로운 표현으로 시작하는 것입니다. 훌륭한 검색 경험을 개발하면, 가장 일반적인 표현식에 최적화하여 가장 정확도가 높은 결과 세트를 큐레이팅할 수 있게 됩니다. 모든 것은 빈 캔버스, 즉 검색 상자로 시작합니다.

사용 가능한 검색 상자를 만들기 위해, queryString이라는 속성을 상태에 추가하겠습니다. 계속해서 queryString을 새 스트링로 업데이트하기 위해, updateQuery 메서드를 만들겠습니다. 사용자가 상자의 텍스트를 변경할 때마다 onChange 핸들러를 활용하여 queryString을 업데이트하고 새 검색을 트리거하게 됩니다.

이제 전체 앱 클래스는 다음과 같습니다.

//src/App.js
// ... 끝이 잘림!
class App extends Component {
state = {
// 검색 상자에서 값을 추적하는 새 상태 속성
queryString: "",
response: null
};
componentDidMount() {
// "node"에 대한 하드 코딩 검색을 제거
this.performQuery(this.state.queryString);
}
// 사용자가 검색 상자에 입력할 때마다 onChange 이벤트를 처리.
updateQuery = e => {
const queryString = e.target.value;
this.setState(
{
queryString // 사용자가 입력한 쿼리 스트링 저장
},
() => {
this.performQuery(queryString); // 새 검색 트리거
}
);
};
performQuery = queryString => {
client.search(queryString, {}).then(
response => {
this.setState({
response
});
},
error => {
console.log(`error: ${error}`);
}
);
};
render() {
const {response, queryString} = this.state;
if (!response) return null;
return (


Node Module Search



{/*쿼리 스트링 값과 onChange 처리기에 연결된
검색 상자*/}

);
}
}
// ... 끝이 잘림!

디바운스!

이 반복 내에서, 상자 내에서 변경 사항이 탐지될 때마다 검색이 발생하게 됩니다. 이는 시스템에 집중될 수 있습니다. 이 문제를 해결하기 위해 Lodash에서 제공하는 _debounce_ 함수를 적용하겠습니다

$ npm install --save lodash

디바운스는 정의된 시간(밀리초)을 기준으로 인바운드 요청의 수를 속도 제한하는 방법입니다. 사용자는 쿼리의 구문을 구성하는 방법이나, 오타를 내는 것이나, 매우 빠르게 타이핑하는 것에 대해 생각하고 있습니다. 따라서 모든 탐지된 변경 사항에 대해 쿼리할 필요는 없습니다.

Lodash의 디바운스 함수 내에서 performQuery 메서드를 끝내면, 200ms 속도 제한을 지정할 수 있습니다. 200ms는 다음 검색 쿼리가 시작되기 전에 입력 없이 경과해야 합니다.

//App.js
// ... 끝이 잘림!
import { debounce } from "lodash"; // 디바운스 가져오기
// ... 끝이 잘림!
performQuery = debounce(queryString => {
client.search(queryString, {}).then(
response => {
this.setState({
response
});
},
error => {
console.log(`error: ${error}`);
}
);
}, 200); // 200밀리초.
// ... 끝이 잘림!

서버를 중단시키는 것 외에도, 속도 제한은 사용자의 쿼리를 원활하게 처리하는 데 도움이 될 수 있습니다. 큰 도움이 됩니다! 느낌이 중요합니다.

다음 단계는…

이것은 고품질의 React화된 검색 경험의 시작입니다. 앞으로 추가할 훌륭한 것들이 많이 있습니다. 스타일 지정을 추가하거나 패싯, 큐레이션 또는 정확도 조정과 같은 동적 App Search 기능을 구현할 수 있습니다. 또는 Analytics API Suite를 살펴보면서 사용자 검색 활동에 대한 가치 있는 인사이트를 발굴할 수 있습니다.

정말 깊이 들어가고 싶다면, 마스터 브랜치 내의 README는 URI 기반의 상태 관리를 만들고, 슬릭 스타일 지정을 추가하고, 페이지네이션, 필터링 및 패싯 검색을 추가하기 위해 튜토리얼을 확장합니다. 몇 가지 스타일을 사용자 지정하면, 고품질 검색 경험의 기반이 될 수 있습니다.

요약

과거에는, 정확도가 높고 보람 있는 애플리케이션 검색을 구성하는 것이 복잡했습니다. Elastic App Search는 가치 있고 조정 가능한 검색을 웹 애플리케이션에 작성하는 편리한 관리형 방법입니다. 제일 좋은 점은 무엇일까요? 엔지니어와 기술 수준이 낮은 이해 관계자 양쪽 모두 매끄럽고 직관적인 대시보드 내에서 주요 기능을 관리할 수 있습니다. 신용 카드가 필요 없는 14일 무료 체험판으로 App Search를 바로 시작하실 수 있습니다.