[mulli] 3. serverpod, postgres 세팅

ds-k.dev·2025년 1월 22일
0

mulli 프로젝트

목록 보기
3/4

Serverpod 세팅

serverpod create mypod
  • serverpod create 명령어를 통해 프로젝트 세팅을 시작할수 있다.


1. mypod_server: 이 패키지에는 서버 측 코드가 포함되어 있다. 서버에 필요한 새 엔드포인트나 기타 기능을 추가할 수 있다.
2. mypod_client: 서버와 통신하는 데 필요한 코드. 일반적으로 이 패키지의 모든 코드는 자동으로 생성되므로 이 패키지의 파일을 편집해서는 안 된다.
3. mypod_flutter: 로컬 서버에 연결하도록 미리 구성된 Flutter 앱.

이렇게 세개의 디렉토리가 자동 생성되게 된다.

DB 세팅

serverpod 에 미리 적혀져 있는대로는, postgres와의 연결을 docker를 통해 하도록 되어있다.

services:
  # Development services
  postgres:
    image: postgis/postgis:16-3.5
    ports:
      - "8090:5432"
    environment:
      POSTGRES_USER: postgres
      POSTGRES_DB: <db name>
      POSTGRES_PASSWORD: <db password>
    volumes:
      - mulli_data:/var/lib/postgresql/data
    profiles:
      - "" # Default profile
      - dev
  redis:
    image: redis:6.2.6
    ports:
      - "8091:6379"
    command: redis-server --requirepass <redis password>
    environment:
      - REDIS_REPLICATION_MODE=master
    profiles:
      - "" # Default profile
      - dev

  # Test services
  postgres_test:
    image: postgres:16.3
    ports:
      - "9090:5432"
    environment:
      POSTGRES_USER: postgres
      POSTGRES_DB: <test db name>
      POSTGRES_PASSWORD: <test db password>
    volumes:
      - mulli_test_data:/var/lib/postgresql/data
    profiles:
      - "" # Default profile
      - test
  redis_test:
    image: redis:6.2.6
    ports:
      - "9091:6379"
    command: redis-server --requirepass <test redis password>
    environment:
      - REDIS_REPLICATION_MODE=master
    profiles:
      - "" # Default profile
      - test

volumes:
  mulli_data:
  mulli_test_data:
  • 자동으로 postgre와 redis를 담은 컨테이너를 설치하는 docker-compose.yaml 파일이 생성됨으로,
docker compose up --build --detach

를 실행하여 db를 실행시켜주면 된다.

  • docker 앱 화면

이렇게 잘 실행이 되고, docker-compose.yaml에 적힌 내용을 바탕으로 DB를 열어보면(DBeaver를 사용했다.) 잘 연결이 되어있을 것이다.

스키마 적용

  1. yaml 파일 작성
    /lib/src/models 에 생성하고자 하는 table을 입력한다.
class: Users
table: users

fields:
  socialId: String
  socialType: String
  name: String
  email: String
  userProfileUrl: String
  createdAt: DateTime
  updatedAt: DateTime

위는 users의 예시인데, server에서 사용할 class와 db에서 사용할 table를 동시에 설정한다고 이해하면 된다.

  1. serverpod generate

Endpoint와 models가 변경되었을경우 실행하는 명령어

serverpod generate 는 db 테이블의 스키마를 서버팟에서 사용할 class로 정의하는 파일들을 자동으로 생성해준다.


abstract class Users implements _i1.TableRow, _i1.ProtocolSerialization {
  Users._({
    this.id,
    required this.socialId,
    required this.socialType,
    required this.name,
    required this.email,
    required this.userProfileUrl,
    required this.createdAt,
    required this.updatedAt,
  });

  factory Users({
    int? id,
    required String socialId,
    required String socialType,
    required String name,
    required String email,
    required String userProfileUrl,
    required DateTime createdAt,
    required DateTime updatedAt,
  }) = _UsersImpl;

  factory Users.fromJson(Map<String, dynamic> jsonSerialization) {
    return Users(
      id: jsonSerialization['id'] as int?,
      socialId: jsonSerialization['socialId'] as String,
      socialType: jsonSerialization['socialType'] as String,
      name: jsonSerialization['name'] as String,
      email: jsonSerialization['email'] as String,
      userProfileUrl: jsonSerialization['userProfileUrl'] as String,
      createdAt:
          _i1.DateTimeJsonExtension.fromJson(jsonSerialization['createdAt']),
      updatedAt:
          _i1.DateTimeJsonExtension.fromJson(jsonSerialization['updatedAt']),
    );
  }

  static final t = UsersTable();

  static const db = UsersRepository._();

  
  int? id;

  String socialId;

  String socialType;

  String name;

  String email;

  String userProfileUrl;

  DateTime createdAt;

  DateTime updatedAt;

  
  _i1.Table get table => t;

  Users copyWith({
    int? id,
    String? socialId,
    String? socialType,
    String? name,
    String? email,
    String? userProfileUrl,
    DateTime? createdAt,
    DateTime? updatedAt,
  });
  
  Map<String, dynamic> toJson() {
    return {
      if (id != null) 'id': id,
      'socialId': socialId,
      'socialType': socialType,
      'name': name,
      'email': email,
      'userProfileUrl': userProfileUrl,
      'createdAt': createdAt.toJson(),
      'updatedAt': updatedAt.toJson(),
    };
  }

  
  Map<String, dynamic> toJsonForProtocol() {
    return {
      if (id != null) 'id': id,
      'socialId': socialId,
      'socialType': socialType,
      'name': name,
      'email': email,
      'userProfileUrl': userProfileUrl,
      'createdAt': createdAt.toJson(),
      'updatedAt': updatedAt.toJson(),
    };
  }

  static UsersInclude include() {
    return UsersInclude._();
  }

  static UsersIncludeList includeList({
    _i1.WhereExpressionBuilder<UsersTable>? where,
    int? limit,
    int? offset,
    _i1.OrderByBuilder<UsersTable>? orderBy,
    bool orderDescending = false,
    _i1.OrderByListBuilder<UsersTable>? orderByList,
    UsersInclude? include,
  }) {
    return UsersIncludeList._(
      where: where,
      limit: limit,
      offset: offset,
      orderBy: orderBy?.call(Users.t),
      orderDescending: orderDescending,
      orderByList: orderByList?.call(Users.t),
      include: include,
    );
  }

  
  String toString() {
    return _i1.SerializationManager.encode(this);
  }
}

class _Undefined {}

class _UsersImpl extends Users {
  _UsersImpl({
    int? id,
    required String socialId,
    required String socialType,
    required String name,
    required String email,
    required String userProfileUrl,
    required DateTime createdAt,
    required DateTime updatedAt,
  }) : super._(
          id: id,
          socialId: socialId,
          socialType: socialType,
          name: name,
          email: email,
          userProfileUrl: userProfileUrl,
          createdAt: createdAt,
          updatedAt: updatedAt,
        );

  
  Users copyWith({
    Object? id = _Undefined,
    String? socialId,
    String? socialType,
    String? name,
    String? email,
    String? userProfileUrl,
    DateTime? createdAt,
    DateTime? updatedAt,
  }) {
    return Users(
      id: id is int? ? id : this.id,
      socialId: socialId ?? this.socialId,
      socialType: socialType ?? this.socialType,
      name: name ?? this.name,
      email: email ?? this.email,
      userProfileUrl: userProfileUrl ?? this.userProfileUrl,
      createdAt: createdAt ?? this.createdAt,
      updatedAt: updatedAt ?? this.updatedAt,
    );
  }
}

class UsersTable extends _i1.Table {
  UsersTable({super.tableRelation}) : super(tableName: 'users') {
    socialId = _i1.ColumnString(
      'socialId',
      this,
    );
    socialType = _i1.ColumnString(
      'socialType',
      this,
    );
    name = _i1.ColumnString(
      'name',
      this,
    );
    email = _i1.ColumnString(
      'email',
      this,
    );
    userProfileUrl = _i1.ColumnString(
      'userProfileUrl',
      this,
    );
    createdAt = _i1.ColumnDateTime(
      'createdAt',
      this,
    );
    updatedAt = _i1.ColumnDateTime(
      'updatedAt',
      this,
    );
  }

  late final _i1.ColumnString socialId;

  late final _i1.ColumnString socialType;

  late final _i1.ColumnString name;

  late final _i1.ColumnString email;

  late final _i1.ColumnString userProfileUrl;

  late final _i1.ColumnDateTime createdAt;

  late final _i1.ColumnDateTime updatedAt;

  
  List<_i1.Column> get columns => [
        id,
        socialId,
        socialType,
        name,
        email,
        userProfileUrl,
        createdAt,
        updatedAt,
      ];
}

class UsersInclude extends _i1.IncludeObject {
  UsersInclude._();

  
  Map<String, _i1.Include?> get includes => {};

  
  _i1.Table get table => Users.t;
}

class UsersIncludeList extends _i1.IncludeList {
  UsersIncludeList._({
    _i1.WhereExpressionBuilder<UsersTable>? where,
    super.limit,
    super.offset,
    super.orderBy,
    super.orderDescending,
    super.orderByList,
    super.include,
  }) {
    super.where = where?.call(Users.t);
  }

  
  Map<String, _i1.Include?> get includes => include?.includes ?? {};

  
  _i1.Table get table => Users.t;
}

class UsersRepository {
  const UsersRepository._();

  Future<List<Users>> find(
    _i1.Session session, {
    _i1.WhereExpressionBuilder<UsersTable>? where,
    int? limit,
    int? offset,
    _i1.OrderByBuilder<UsersTable>? orderBy,
    bool orderDescending = false,
    _i1.OrderByListBuilder<UsersTable>? orderByList,
    _i1.Transaction? transaction,
  }) async {
    return session.db.find<Users>(
      where: where?.call(Users.t),
      orderBy: orderBy?.call(Users.t),
      orderByList: orderByList?.call(Users.t),
      orderDescending: orderDescending,
      limit: limit,
      offset: offset,
      transaction: transaction,
    );
  }

... (db 쿼리 메서드 생략...)

길지만, 플러터, db와 통신할때 쓰일 수 있는 각종 메소드를 자동으로 정의해준다.

  1. migration 파일 생성
serverpod create-migration

이제 정의된 models와 generated된 파일을 바탕으로 migration 파일을 생성해준다.

-- ACTION CREATE TABLE
--
CREATE TABLE "users" (
    "id" bigserial PRIMARY KEY,
    "socialId" text NOT NULL,
    "socialType" text NOT NULL,
    "name" text NOT NULL,
    "email" text NOT NULL,
    "userProfileUrl" text NOT NULL,
    "createdAt" timestamp without time zone NOT NULL,
    "updatedAt" timestamp without time zone NOT NULL
);

이렇게 정의한대로 query문을 생성해준다.

  1. migration apply
dart bin/main.dart --apply-migrations

해당 명령어를 통해 마이그레이션 apply해주고 서버를 실행하면 새롭게 작성한 db 정보들이 업데이트되게 된다.

다음은 연결..!

다음 과제는 db와 통신하고, 이를 endpoint로 만들어, flutter에서 활용하는 것을 가져오도록 하겠다!

0개의 댓글