페이지 자체에서 데이터베이스 쿼리를 추상화해야합니까?


10

PHP로 페이지 생성을 작성할 때 데이터베이스 쿼리로 가득 찬 파일 세트를 작성하는 경우가 종종 있습니다. 예를 들어, 다음과 같이 페이지에 표시하기 위해 데이터베이스에서 직접 게시물에 대한 일부 데이터를 가져 오는 쿼리가있을 수 있습니다.

$statement = $db->prepare('SELECT * FROM posts WHERE id=:id');
$statement->bindValue(':id', $id, PDO::PARAM_INT);
$statement->execute();
$post = $statement->fetch(PDO::FETCH_ASSOC);
$content = $post['content']
// do something with the content

이러한 빠른 일회성 쿼리는 일반적으로 작지만 때로는 데이터베이스 상호 작용 코드의 많은 부분으로 끝나는 경우가 있습니다.

경우에 따라 포스트 관련 db 쿼리를 처리하는 간단한 함수 라이브러리를 만들어이 코드 블록을 간단한 것으로 단축하여이 문제를 해결했습니다.

$content = post_get_content($id);

그리고 그것은 훌륭합니다. 아니면 적어도 다른 일을해야 할 때까지입니다. 가장 최근의 5 개의 게시물을 목록에 표시해야 할 수도 있습니다. 글쎄, 나는 항상 다른 기능을 추가 할 수 있었다 :

$recent_posts = post_get_recent(5);
foreach ($recent_posts as $post) { ... }

그러나 그것은 SELECT *일반적으로 실제로 필요하지 않지만 종종 추상화하기에는 너무 복잡합니다. 결국 모든 단일 사용 사례에 대한 방대한 데이터베이스 상호 작용 함수 라이브러리 또는 모든 페이지 코드 내부의 복잡한 쿼리로 끝납니다. 이 라이브러리를 만든 후에도 이전에는 사용하지 않은 작은 조인을 수행해야하며 작업을 수행하기 위해 고도로 전문화 된 다른 기능을 작성해야합니다.

물론, 일반적인 유스 케이스 및 특정 상호 작용에 대한 쿼리 기능을 사용할 수는 있지만 원시 쿼리를 작성하자마자 모든 것에 직접 액세스하기 시작합니다. 어쨌든, 게으르고 MySQL 쿼리에서 직접 수행 해야하는 PHP 루프에서 작업을 시작합니다.

인터넷 응용 프로그램을 작성하는 데 더 경험이 많은 사람들에게 물어보고 싶습니다. 추가 코드 줄과 추상화로 인해 발생할 수있는 비 효율성에 대한 유지 관리 능력 향상이 있습니까? 아니면 단순히 직접 쿼리 문자열을 사용하여 데이터베이스 상호 작용을 처리하는 데 적합한 방법입니까?


어쩌면 저장 프로 시저를 사용하여 지저분한 select
것들을

답변:


7

특화된 쿼리 함수가 너무 많으면이를 구성 가능한 비트로 분리 할 수 ​​있습니다. 예를 들어

$posts = posts()->joinWithComments()->orderBy("post.post_date")->first(5);

명심해야 할 추상화 수준 계층도 있습니다. 당신은

  1. MySQL의 API
  2. 당신 같은 선택로 MySQL 기능 ( "게시물에서 선택 * 여기서 foo는 = 바"); 또는 아마도 더 작곡select("posts")->where("foo = bar")->first(5)
  3. 예를 들어 응용 프로그램 도메인과 관련된 기능 posts()->joinWithComments()
  4. 특정 페이지에 고유 한 기능 (예 : commentsToBeReviewed($currentUser)

이 추상화 순서를 존중하기 위해 유지 관리가 용이하다는 점에서 많은 비용을 지불합니다. 페이지 스크립트는 레벨 4 기능 만 사용해야하고 레벨 4 기능은 레벨 3 기능 등으로 작성해야합니다. 이 작업에는 시간이 조금 더 걸리지 만 시간이지나면서 유지 관리 비용을 일정하게 유지하는 데 도움이됩니다 ( "오 마이 갓, 또 다른 변화를 원합니다 !!!").


2
+1이 구문은 본질적으로 고유 한 ORM을 만듭니다. 데이터베이스 액세스가 복잡 해지는 것에 대해 정말로 걱정하고 세부 사항을 다루는 데 많은 시간을 소비하지 않으려면 이미이 자료를 파악한 성숙한 웹 프레임 워크 (예 : CodeIgniter )를 사용하는 것이 좋습니다 . 또는 적어도 xpmatteo가 ​​보여준 것과 비슷한 종류의 구문 설탕을보기 위해 그것을 사용해보십시오.
Hartley Brody

5

우려의 분리는 읽을 가치가있는 원칙입니다. 이에 관한 Wikipedia 기사를 참조하십시오.

http://en.wikipedia.org/wiki/Separation_of_concerns

읽을 가치가있는 또 다른 원칙은 커플 링입니다.

http://en.wikipedia.org/wiki/Coupling_(computer_science )

두 가지 뚜렷한 우려가 있습니다. 하나는 데이터베이스에서 데이터를 마샬링하고 다른 하나는 해당 데이터를 렌더링하는 것입니다. 실제로 간단한 응용 프로그램에서는 걱정할 필요가 거의 없으므로 데이터베이스 액세스 및 관리 계층을 렌더링 계층과 밀접하게 결합했지만 작은 응용 프로그램의 경우 큰 문제가 아닙니다. 문제는 웹 응용 프로그램이 발전하는 경향이 있으며 성능이나 기능과 같은 방식으로 웹 응용 프로그램을 확장하려는 경우 몇 가지 문제가 발생한다는 것입니다.

사용자 생성 주석의 웹 페이지를 생성한다고 가정 해 봅시다. 뾰족한 머리 보스와 함께 네이티브 애플 리케이션, 즉 iPhone / Android 등을 지원하도록 요청합니다. JSON 출력이 필요합니다. 이제 HTML을 생성하는 렌더링 코드를 추출해야합니다. 이 작업을 완료하면 두 개의 렌더링 엔진이 포함 된 데이터 액세스 라이브러리가 제공되며 모든 것이 정상적입니다. 기능적으로 확장되었습니다. 비즈니스 로직과 렌더링을 모두 분리하여 관리했을 수도 있습니다.

함께 상사가 와서 웹 사이트에 게시물을 표시하려는 고객이 있고 XML이 필요하며 초당 최대 약 5000 건의 요청이 필요하다는 것을 알려줍니다. 이제 XML / JSON / HTML을 생성해야합니다. 이전과 마찬가지로 렌더링을 다시 분리 할 수 ​​있습니다. 그러나 필요한 성능을 편안하게 얻으려면 100 대의 서버를 추가해야합니다. 이제 서버 당 수십 개의 연결이있는 100 대의 서버에서 데이터베이스가 공격 당하고 있습니다. 각 서버는 서로 다른 요구 사항과 쿼리가 다른 세 개의 다른 앱에 직접 노출됩니다. 하나하지만 나는 가지 않을 것입니다. 이제 성능에 맞게 확장해야합니다. 각 앱마다 서로 다른 캐싱 요구 사항, 즉 서로 다른 우려 사항이 있습니다. 데이터베이스 액세스 / 비즈니스 로직 / 렌더링 계층과 같이 밀접하게 결합 된 계층에서이를 시도하고 관리 할 수 ​​있습니다. 각 계층의 문제는 이제 서로를 방해하기 시작했습니다. 즉, 데이터베이스에서 데이터의 캐싱 요구 사항이 렌더링 계층과 매우 다를 수 있습니다. 비즈니스 계층에있는 논리는 SQL 즉, 뒤로 이동하거나 렌더링 레이어로 피가 흘릴 수 있습니다. 이것은 한 레이어에 모든 것을 갖는 것으로 보았던 가장 큰 문제 중 하나입니다. 강철 콘크리트를 응용 프로그램에 붓는 것과 같은 좋은 방법은 아닙니다.

이러한 유형의 문제에 접근하는 표준 방법, 즉 웹 서비스의 HTTP 캐싱 (오징어 / yts 등)이 있습니다. memcached / redis와 같은 웹 서비스 자체 내 응용 프로그램 수준 캐싱. 또한 데이터베이스를 확장하기 시작할 때 (예 : 여러 읽기 호스트와 하나의 마스터 또는 호스트 전체의 샤드 된 데이터) 문제가 발생할 수 있습니다. 사용자 "usera"가 모든 쓰기 요청에 대해 "[table / database] foo"에 해시하는 경우 쓰기 또는 읽기 요청 또는 샤드 된 데이터베이스에 따라 달라지는 데이터베이스에 대한 다양한 연결을 관리하는 100 개의 호스트를 원하지 않습니다.

관심사의 분리는 언제 어디에서 할 것인지를 선택하는 것이 건축 결정이며 약간의 예술입니다. 매우 다른 요구 사항을 갖도록 발전 할 수있는 것을 단단히 결합하지 마십시오. 사물을 별도로 유지해야하는 여러 가지 이유가 있습니다. 즉, 테스트, 변경 사항 배포, 보안, 재사용, 유연성 등을 단순화합니다.


나는 당신이 어디에서 왔는지 이해하고, 당신이 말한 것에 동의하지 않지만, 이것은 지금 작은 관심사입니다. 문제의 프로젝트는 개인적인 프로젝트이며 현재 모델과 관련한 대부분의 문제는 타이트한 커플 링을 피하기 위해 프로그래머의 본능에서 비롯되었지만 서버 측 개발의 복잡한 초보자이므로 실제로 끝났습니다. 내 머리 위로. 그래도이 프로젝트에서 완전히 따르지 않더라도 좋은 조언으로 보이는 것에 대해 +1입니다.
Alexis King

당신이하고있는 일이 작게 유지된다면 가능한 한 간단하게 유지할 것입니다. 여기에 따르는 좋은 원칙은 YAGNI 입니다.
Harry

1

"페이지 자체"라고 말하면 HTML을 동적으로 생성하는 PHP 소스 파일을 의미한다고 가정합니다.

데이터베이스를 쿼리하지 않고 동일한 소스 파일에서 HTML을 생성하십시오.

데이터베이스를 쿼리하는 소스 파일은 PHP 소스 파일이지만 "페이지"가 ​​아닙니다.

HTML을 동적으로 생성하는 PHP 소스 파일에서 데이터베이스에 액세스하는 PHP 소스 파일에 정의 된 함수를 호출하기 만하면됩니다.


0

대부분의 중간 규모 프로젝트에 사용하는 패턴은 다음과 같습니다.

  • 모든 SQL 쿼리는 별도의 위치에 서버 측 코드와 분리되어 있습니다.

    C #을 사용한다는 것은 부분 클래스를 사용하는 것을 의미합니다. 즉, 단일 클래스에서 쿼리에 액세스 할 수있는 경우 쿼리를 별도의 파일에 넣는 것을 의미합니다 (아래 코드 참조).

  • 이러한 SQL 쿼리는 상수 입니다. 이는 SQL 쿼리를 즉시 구축하려는 유혹을 막기 때문에 중요합니다 (따라서 SQL 주입의 위험이 증가하고 동시에 쿼리를 검토하기가 더 어려워집니다).

C #의 현재 접근 방식

예:

// Demo.cs
public partial class Demo : DataRepository
{
    public IEnumerable<Stuff> LoadStuff(int categoryId)
    {
        return this
            .Query(Queries.LoadStuff)
            .With(new { CategoryId = categoryId })
            .ReadRows<Stuff>();
    }

    // Other methods go here.
}

public partial class Demo
{
    private static class Queries
    {
        public const string LoadStuff = @"
select top 100 [StuffId], [SomeText]
    from [Schema].[Table]
    where [CategoryId] = @CategoryId
    order by [CreationUtcTime]";

        // Other queries go here.
    }
}

이 접근 방식은 쿼리를 별도의 파일로 만드는 이점이 있습니다. 이를 통해 DBA는 개발자가 수행 한 커밋과 충돌하지 않고 쿼리를 검토 및 수정 / 최적화하고 수정 된 파일을 소스 제어에 커밋 할 수 있습니다.

관련된 이점은 소스 제어가 조회를 포함하는 파일로만 DBA의 액세스를 제한하고 나머지 코드에 대한 액세스를 거부하는 방식으로 구성 될 수 있다는 것입니다.

PHP로 할 수 있습니까?

PHP에는 부분 클래스와 내부 클래스가 모두 없으므로 PHP로 구현할 수 없습니다.

DemoQueries어디서나 클래스에 액세스 할 수있는 경우 상수를 포함 하는 별도의 정적 클래스 ( )를 사용하여 별도의 파일을 만들 수 있습니다 . 또한 전역 범위를 오염시키지 않기 위해 모든 쿼리 클래스를 전용 네임 스페이스에 넣을 수 있습니다. 이것은 다소 장황한 구문을 만들지 만 자세한 내용을 피할 수는 없습니다.

namespace Data {
    public class Demo inherit DataRepository {
        public function LoadStuff($categoryId) {
            $query = \Queries\Demo::$LoadStuff;
            // Do the stuff with the query.
        }

        // Other methods go here.
    }
}

namespace Queries {
    public static class Demo {
        public const $LoadStuff = '...';

        // Other queries go here.
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.