Laravel Scout: 검색 전용 단일 API 엔드포인트 생성

20052 단어 scoutlaravelsearchapi
오늘 저는 저희 팀이 현재 작업 중인 애플리케이션에서 검색이 작동하는 방식을 개선해 달라는 요청을 받았습니다.

현재 구현된 검색 메커니즘은 다소 느립니다. 성능 문제를 말할 수 있습니다.

그런 다음 데이터베이스에서 모든 검색을 수행할 수 있는 단일 API인 API를 만들 수 있는지 생각했습니다. 즉, 사용자, 게시물, 주문 등과 같은 모든 대상 테이블을 의미합니다.

그래서 Laravel Scout을 살펴보고 평소처럼 설치합니다.

내가 구성하는 유일한 것은 드라이버입니다. 데이터베이스 드라이버와 검색 가능한 키를 사용합니다.

TLDR;

composer require laravel/scout


그런 다음 업데이트.env
SCOUT_DRIVER=database

\App\Models\User를 설정합니다.


use Illuminate\Contracts\Auth\MustVerifyEmail;
use Laravel\Jetstream\HasProfilePhoto;
use Laravel\Sanctum\HasApiTokens;
use Laravel\Scout\Searchable;

class User extends Authenticatable implements MustVerifyEmail
{
    use HasApiTokens;
    use HasProfilePhoto;
    use Searchable;

    /**
     * Get the indexable data array for the model.
     *
     * @return array
     */
    public function toSearchableArray()
    {
        return [
            'name' => $this->name,
            'email' => $this->email,
        ];
    }
}


그래서 위와 같이 Laravel Scout를 구성했습니다.

중요한 부분은 API 엔드포인트입니다. 알림, 게시물, 주문 등을 검색하려는 경우를 대비하여 API 끝점은 모든 종류의 검색에 사용할 수 있어야 합니다. 모든 것이 하나의 단일 API 끝점에 있기를 원합니다 - /search .

그래서 제가 처음에 한 방법은 다음과 같습니다.


<?php

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::middleware('auth:sanctum')
    ->get('/search', function (Request $request) {
        return User::search($request->search)->first();
    });


그러나 이렇게 하면 하나의 모델과 첫 번째 레코드만 검색할 수 있습니다.
\App\Models\Post와 유사하게 구성된 \App\Models\User 모델이 있다고 상상해보십시오. API 끝점을 더 동적으로 만들려면 어떻게 해야 합니까?

<?php

use App\Models\Post;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::middleware('auth:sanctum')
    ->get('/search', function (Request $request) {
        return match($request->type) {
            'user' => User::search($request->search)->first(),
            'post' => Post::search($request->search)->first(),
        }
    });


하지만 이 경로에는 너무 많은 하드코딩이 있어 미래에 유지하기가 어렵습니다.

그래서 약간 리팩토링을 해봅시다. 우리는 match 부분을 다른 곳으로 빼낼 것입니다.


<?php

use Laravel\Scout\Searchable;

if (! function_exists('search')) {
    function search(string $type, string $keyword, bool $paginate = false)
    {
        abort_if(
            ! class_exists($type), 
            "Class $type not exists."
        );

        throw_if(
            ! in_array(
                Searchable::class, 
                class_uses_recursive($type)
            )
        );

        $class = $type;

        $query = $class::search($keyword);

        return $paginate
            ? $query->paginate()
            : $query->first();
    }
}

abort_if()는 우리가 사용할 클래스가 존재하는지 확인하기 위한 것입니다.
throw_if() , 우리는 Laravel Scout Searchable 트레이트만 엄격히 허용합니다.

따라서 새 경로는 다음과 같습니다.


use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::middleware('auth:sanctum')->get('/search', function (Request $request) {
    return search(
        $request->type, 
        $request->search, 
        $request->query('paginate', false)
    );
});


하지만! 한 부분이 더 빠졌습니다. 해당 유형이 어떤 모델에 속하는지 어떻게 알 수 있습니까? 이를 수행하는 방법에는 여러 가지가 있습니다. 유형과 모델 사이에 간단한 매핑이 수행됩니다.

내가 Spatie의 Laravel Enum을 사용하는 방법과 내 경로는 다음과 같습니다.

<?php

use App\Enums\SearchType;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::middleware('auth:sanctum')->get('/search', function (Request $request) {
    abort_if(
        empty($request->type) || empty(SearchType::tryFrom($request->type)),
        404,
        'Unknown search type'
    );

    abort_if(
        empty($request->search),
        404,
        'Please provide search keyword'
    );

    return search(SearchType::tryFrom($request->type), $request->search, $request->query('paginate', false));
});


여기서 SearchType:

<?php

namespace App\Enums;

use Spatie\Enum\Laravel\Enum;

/**
 * @method static self user()
 * @method static self profile()
 */
class SearchType extends Enum
{
    public static function values(): array
    {
        return [
            'user' => \App\Models\User::class,
            'profile' => \Profile\Models\Profile::class,
        ];
    }

    protected static function labels(): array
    {
        return [
            'user' => __('User'),
            'profile' => __('Profile'),
        ];
    }
}


그리고 이 새 열거형에 의존하도록 내 도우미search()를 업데이트해야 합니다.

<?php

use App\Enums\SearchType;
use Laravel\Scout\Searchable;

if (! function_exists('search')) {
    function search(SearchType $type, string $keyword, bool $paginate = false)
    {
        throw_if(
            ! in_array(Searchable::class, class_uses_recursive($type->value))
        );

        $class = $type->value;

        $query = $class::search($keyword);

        return $paginate
            ? $query->paginate()
            : $query->first();
    }
}


이제 모든 것이 준비되었으니 나중에 Laravel\Scout\Searchable 특성을 모든 모델에 추가하고 새로운 유형의 검색을 허용하도록 내 App\Enums\SearchType를 업데이트할 수 있습니다.

따라서 사용법:

curl --request GET \
  --url 'http://127.0.0.1:8000/api/search?type=profile&search=00000000' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer WD7wV13lCSp1XqesClDsND4IP92OPiMwC9soWuS6'


응답은 다음과 같습니다.

{
    "uuid": "82dea65f-38bd-4ef6-a4cd-c3bffa3b3b2f",
    "profile_no": "00000000",
    "name": "Superadmin",
    "created_at": "2022-09-15T05:01:20.000000Z",
    "updated_at": "2022-09-15T05:01:20.000000Z"
}


이것이 응용 프로그램에 대한 더 나은 코드를 작성하는 방법에 대한 통찰력을 제공하기를 바랍니다.

하루가 끝날 무렵에는 검색 API를 제공 및 유지 관리하고 성능 문제에 대해 우리 팀을 위한 더 나은 솔루션을 갖게 되었습니다. 팀이 이 접근 방식으로 전환하고 시도해 볼 수 있도록 합니다.

Don't forget to make use of the API Resource.
You can change to Agnolia if you want to, but let's keep it for database.
Database driver only support MySQL and PostgreSQL at the moment.

좋은 웹페이지 즐겨찾기