レイヤードアーキテクチャとは、プログラムを役割ごとの層(レイヤー)に分け、層の間の依存を一方向にそろえる構成のことです。 画面や API を受け持つ層、業務ロジックの層、データ取得の層の3つに分ける形がよく知られています。

この記事では、オニオン・ヘキサゴナル・クリーンアーキテクチャとの違いを早見表で示したあと、同じ会員登録機能を「3層レイヤード」と「依存を逆にした構成」の両方で実装し、ディレクトリ構成・import 文・実行結果を並べて比べます。層の向きに反する import を自動で検出する設定例も載せています。用語の背景よりも、構成とコードに絞った内容です。

確認環境(確認日 2026-10-05):Ubuntu 24.04、Python 3.11.15(標準ライブラリの sqlite3、unittest)、import-linter 2.15。図は Mermaid CLI(@mermaid-js/mermaid-cli 12.0.0)で描画できることを確認しています。

この記事の結論

  • 違いは層の数ではなく依存の向きです。 データ取得の層に業務ロジックが依存するのが従来のレイヤード、インターフェースで向きを逆にしたものがオニオン・ヘキサゴナル・クリーンです。
  • 小規模で CRUD(登録・参照・更新・削除)中心なら、3層レイヤードで足ります。
  • 業務ルールが多く、テストでデータベースを切り離したいなら、依存を逆にします。
  • どちらを選んでも、層の向きに反する import はツールで検出しておくと、構成が保たれます。

4つの方式の早見表

方式 層・部品の名前 依存の向き 業務ロジックは DB を知っているか
レイヤード(3層) プレゼンテーション → ビジネスロジック → データアクセス 上の層から下の層へ 知っている(データアクセス層を直接呼ぶ)
オニオン 中心にドメインモデル、その外側にリポジトリのインターフェースを含む層、最も外側に UI・インフラ・テスト 外側から中心へ 知らない(インターフェースだけを知る)
ヘキサゴナル(ポートとアダプター) 内側にアプリケーション、境界にポート、外側にアダプター 外側のアダプターから内側へ 知らない(ポートだけを知る)
クリーン 内側から Entities、Use Cases、Interface Adapters、Frameworks and Drivers 外側の円から内側の円へ 知らない(内側は外側を一切知らない)

層の名前は、オニオンは Jeffrey Palermo 氏の 2008 年の記事、ヘキサゴナルは Alistair Cockburn 氏の記事、クリーンは Robert C. Martin 氏の 2012 年の記事の記載に基づいています(記事末尾の参考・出典)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「レイヤードアーキテクチャとは:依存関係とディレクトリ構成例・クリーンとの違い」

依存の向きを図で比べる

矢印は「import する側 → される側」を表します。

3層レイヤード

flowchart TD
    P[プレゼンテーション層<br>presentation] --> B[ビジネスロジック層<br>business]
    B --> D[データアクセス層<br>data]
    D --> DB[(データベース)]

業務ロジックがデータアクセス層を import します。データベースの扱いを変えると、その上の層に影響が届く向きです。オニオンアーキテクチャの記事は、UI と業務ロジックがデータアクセスに結び付くことを、従来の層構成でいちばん多く見られる点として挙げています。

依存を逆にした構成

flowchart TD
    P[プレゼンテーション層<br>presentation] --> A[アプリケーション層<br>application<br>手順とインターフェース]
    I[インフラ層<br>infrastructure<br>SQLite での実装] --> A
    A --> DM[ドメイン層<br>domain<br>業務ルール]
    I --> DM
    I --> DB[(データベース)]
    M[main.py<br>組み立て役] -.-> P
    M -.-> I
    M -.-> A

インフラ層が、内側で宣言されたインターフェースを実装します。矢印はすべて内側(ドメイン層)へ向かい、ドメイン層から出る矢印はありません。 クリーンアーキテクチャの記事はこの決まりを「依存性のルール」と呼び、ソースコードの依存は内側にだけ向かい、内側の円は外側の円について何も知らない、と説明しています。

例1:3層レイヤードで書く会員登録

架空の会員登録機能を題材にします。ルールは3つです。名前は空にできない、メールアドレスには @ が必要、同じメールアドレスは登録できない。

ディレクトリ構成

layered_app/
├── .importlinter
├── main.py
└── membership/
    ├── __init__.py
    ├── business/
    │   ├── __init__.py
    │   └── member_service.py
    ├── data/
    │   ├── __init__.py
    │   └── member_dao.py
    └── presentation/
        ├── __init__.py
        └── cli.py

コード

membership/data/member_dao.py

"""データアクセス層:SQLite に会員を保存する"""
import sqlite3


class MemberDao:
    def __init__(self, db_path):
        self.conn = sqlite3.connect(db_path)
        self.conn.execute(
            "CREATE TABLE IF NOT EXISTS members ("
            "id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT NOT NULL UNIQUE)"
        )

    def exists_by_email(self, email):
        row = self.conn.execute("SELECT 1 FROM members WHERE email = ?", (email,)).fetchone()
        return row is not None

    def insert(self, name, email):
        cur = self.conn.execute("INSERT INTO members (name, email) VALUES (?, ?)", (name, email))
        self.conn.commit()
        return cur.lastrowid

membership/business/member_service.py

"""ビジネスロジック層:会員登録のルール。データアクセス層を直接 import する"""
from membership.data.member_dao import MemberDao


class RegistrationError(Exception):
    pass


class MemberService:
    def __init__(self, db_path):
        self.dao = MemberDao(db_path)          # 下の層の具体的なクラスを自分で作る

    def register(self, name, email):
        if not name.strip():
            raise RegistrationError("名前を入力してください")
        if "@" not in email:
            raise RegistrationError("メールアドレスの形式が正しくありません")
        if self.dao.exists_by_email(email):
            raise RegistrationError("このメールアドレスは登録済みです")
        return self.dao.insert(name.strip(), email)

membership/presentation/cli.py

"""プレゼンテーション層:入力を受け取り、結果を表示する"""
from membership.business.member_service import MemberService, RegistrationError


def run(db_path, requests):
    service = MemberService(db_path)
    for name, email in requests:
        try:
            member_id = service.register(name, email)
            print(f"登録しました: id={member_id} {name} <{email}>")
        except RegistrationError as e:
            print(f"登録できません: {e}")

main.py

from membership.presentation.cli import run

run(":memory:", [
    ("山田 太郎", "taro@example.com"),
    ("山田 次郎", "taro@example.com"),
    ("鈴木 花子", "hanako.example.com"),
])

実行結果

cd layered_app
python3 main.py
登録しました: id=1 山田 太郎 <taro@example.com>
登録できません: このメールアドレスは登録済みです
登録できません: メールアドレスの形式が正しくありません

ファイルは3つ、import は上から下への2本だけで、流れを追いやすい構成です。一方で、member_service.py の先頭に from membership.data.member_dao import MemberDao があるため、業務ロジックを読み込むと SQLite のモジュールも一緒に読み込まれます。

python3 -c "import sys; import membership.business.member_service; print('sqlite3' in sys.modules)"
True

例2:依存を逆にした構成で書く会員登録

同じ機能を、依存の向きを逆にして書きます。変えるのは2点です。

  1. 業務ロジックの側に「保存先に求める機能」をインターフェース(Python では typing.Protocol)として宣言する。
  2. 具体的なクラス(SQLite での実装)は、業務ロジックが自分で作らず、外から受け取る。

ディレクトリ構成

inverted_app/
├── .importlinter
├── main.py
├── membership/
│   ├── __init__.py
│   ├── application/
│   │   ├── __init__.py
│   │   ├── ports.py
│   │   └── register_member.py
│   ├── domain/
│   │   ├── __init__.py
│   │   └── member.py
│   ├── infrastructure/
│   │   ├── __init__.py
│   │   └── sqlite_member_repository.py
│   └── presentation/
│       ├── __init__.py
│       └── cli.py
└── tests/
    ├── __init__.py
    └── test_register_member.py

コード

membership/domain/member.py

"""ドメイン層:会員と、会員に関するルール。ほかの層を import しない"""
from dataclasses import dataclass


class RegistrationError(Exception):
    pass


@dataclass(frozen=True)
class Member:
    name: str
    email: str

    @staticmethod
    def create(name, email):
        if not name.strip():
            raise RegistrationError("名前を入力してください")
        if "@" not in email:
            raise RegistrationError("メールアドレスの形式が正しくありません")
        return Member(name=name.strip(), email=email)

membership/application/ports.py

"""アプリケーション層:外側に求める機能をインターフェースとして宣言する"""
from typing import Protocol

from membership.domain.member import Member


class MemberRepository(Protocol):
    def exists_by_email(self, email: str) -> bool: ...

    def add(self, member: Member) -> int: ...

membership/application/register_member.py

"""アプリケーション層:会員登録の手順。知っているのはインターフェースだけ"""
from membership.application.ports import MemberRepository
from membership.domain.member import Member, RegistrationError


class RegisterMember:
    def __init__(self, repository: MemberRepository):
        self.repository = repository           # 具体的なクラスは外から受け取る

    def execute(self, name, email):
        member = Member.create(name, email)
        if self.repository.exists_by_email(member.email):
            raise RegistrationError("このメールアドレスは登録済みです")
        return self.repository.add(member)

membership/infrastructure/sqlite_member_repository.py

"""インフラ層:インターフェースを SQLite で実装する。内側の層を import する"""
import sqlite3

from membership.domain.member import Member


class SqliteMemberRepository:
    def __init__(self, db_path):
        self.conn = sqlite3.connect(db_path)
        self.conn.execute(
            "CREATE TABLE IF NOT EXISTS members ("
            "id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT NOT NULL UNIQUE)"
        )

    def exists_by_email(self, email: str) -> bool:
        row = self.conn.execute("SELECT 1 FROM members WHERE email = ?", (email,)).fetchone()
        return row is not None

    def add(self, member: Member) -> int:
        cur = self.conn.execute(
            "INSERT INTO members (name, email) VALUES (?, ?)", (member.name, member.email)
        )
        self.conn.commit()
        return cur.lastrowid

membership/presentation/cli.py

"""プレゼンテーション層:入力を受け取り、結果を表示する"""
from membership.application.register_member import RegisterMember
from membership.domain.member import RegistrationError


def run(usecase: RegisterMember, requests):
    for name, email in requests:
        try:
            member_id = usecase.execute(name, email)
            print(f"登録しました: id={member_id} {name} <{email}>")
        except RegistrationError as e:
            print(f"登録できません: {e}")

main.py

"""組み立て役:具体的なクラスを選んでつなぐのは、ここだけ"""
from membership.application.register_member import RegisterMember
from membership.infrastructure.sqlite_member_repository import SqliteMemberRepository
from membership.presentation.cli import run

usecase = RegisterMember(SqliteMemberRepository(":memory:"))
run(usecase, [
    ("山田 太郎", "taro@example.com"),
    ("山田 次郎", "taro@example.com"),
    ("鈴木 花子", "hanako.example.com"),
])

実行結果

cd inverted_app
python3 main.py
登録しました: id=1 山田 太郎 <taro@example.com>
登録できません: このメールアドレスは登録済みです
登録できません: メールアドレスの形式が正しくありません

表示は例1と同じです。違いは import に表れます。会員登録の手順を読み込んでも、SQLite のモジュールは読み込まれません。

python3 -c "import sys; import membership.application.register_member; print('sqlite3' in sys.modules)"
False

データベースなしでテストする

インターフェースを満たすテスト用のクラスを渡せば、データベースを用意せずに業務ルールをテストできます。

tests/test_register_member.py

"""データベースを使わずに、会員登録の手順をテストする"""
import sys
import unittest

from membership.application.register_member import RegisterMember
from membership.domain.member import RegistrationError


class InMemoryMemberRepository:
    """テスト用の実装。辞書に保存するだけ"""

    def __init__(self):
        self.members = {}

    def exists_by_email(self, email):
        return email in self.members

    def add(self, member):
        self.members[member.email] = member
        return len(self.members)


class RegisterMemberTest(unittest.TestCase):
    def setUp(self):
        self.repository = InMemoryMemberRepository()
        self.usecase = RegisterMember(self.repository)

    def test_register(self):
        self.assertEqual(self.usecase.execute(" 山田 太郎 ", "taro@example.com"), 1)
        self.assertEqual(self.repository.members["taro@example.com"].name, "山田 太郎")

    def test_duplicate_email(self):
        self.usecase.execute("山田 太郎", "taro@example.com")
        with self.assertRaises(RegistrationError):
            self.usecase.execute("山田 次郎", "taro@example.com")

    def test_invalid_email(self):
        with self.assertRaises(RegistrationError):
            self.usecase.execute("鈴木 花子", "hanako.example.com")

    def test_sqlite3_is_not_imported(self):
        self.assertNotIn("sqlite3", sys.modules)


if __name__ == "__main__":
    unittest.main()
python3 -m unittest -v tests.test_register_member
test_duplicate_email (tests.test_register_member.RegisterMemberTest.test_duplicate_email) ... ok
test_invalid_email (tests.test_register_member.RegisterMemberTest.test_invalid_email) ... ok
test_register (tests.test_register_member.RegisterMemberTest.test_register) ... ok
test_sqlite3_is_not_imported (tests.test_register_member.RegisterMemberTest.test_sqlite3_is_not_imported) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK

2つの構成を import 文で比べる

ファイルの役割 3層レイヤードの import 依存を逆にした構成の import
業務ロジック from membership.data.member_dao import MemberDao from membership.application.ports import MemberRepository(インターフェース)
データの保存 import sqlite3 のみ import sqlite3 と from membership.domain.member import Member
具体的なクラスを作る場所 業務ロジックの __init__ の中 main.py(組み立て役)だけ
ファイル数(__init__.py を除く) 4 6(テストを含めて 7)

依存を逆にすると、保存の実装が業務の側の型(Member)に合わせる形になります。そのぶんファイルは増えます。

層の向きに反する import を検出する設定例

構成は、決めただけでは時間とともに崩れやすいものです。Python では import-linter で、層の向きを設定ファイルに書いて検査できます。

pip install import-linter

3層レイヤードの設定

プロジェクトの直下に .importlinter を置きます。layers は上の層から順に書きます。上の層は下の層を import できますが、逆はできません。

[importlinter]
root_package = membership

[importlinter:contract:layers]
name = 3層は上から下へだけ依存する
type = layers
layers =
    membership.presentation
    membership.business
    membership.data
lint-imports
---------
Contracts
---------

Analyzed 7 files, 2 dependencies.
---------------------------------

3層は上から下へだけ依存する KEPT

Contracts: 1 kept, 0 broken.

(冒頭に出るロゴの表示は省いています。以降も同じです。)

ここで、データアクセス層に次のファイルを足してみます。上の層の例外クラスを import しています。

membership/data/member_exporter.py

"""データアクセス層から、上のビジネスロジック層を import している(向きに反する例)"""
from membership.business.member_service import RegistrationError


def export_members(dao):
    rows = dao.conn.execute("SELECT id, name, email FROM members").fetchall()
    if not rows:
        raise RegistrationError("会員が1件もありません")
    return rows
---------
Contracts
---------

Analyzed 8 files, 3 dependencies.
---------------------------------

3層は上から下へだけ依存する BROKEN

Contracts: 0 kept, 1 broken.

----------------
Broken contracts
----------------

3層は上から下へだけ依存する
--------------

membership.data is not allowed to import membership.business:

- membership.data.member_exporter -> membership.business.member_service (l.2)

どのファイルの何行目が向きに反しているかが表示され、終了コードは 1 になります。CI(自動チェック)に組み込めば、マージ前に気づけます。

依存を逆にした構成の設定

同じ高さに並ぶ層は | で区切って1行に書きます。この書き方では、並べた層どうしの import も検査の対象になります。さらに、内側の層が sqlite3 を import しないことを forbidden で指定します。外部パッケージを検査に含めるには include_external_packages = True が必要です。

[importlinter]
root_package = membership
include_external_packages = True

[importlinter:contract:layers]
name = 依存は外側から内側へだけ向かう
type = layers
layers =
    membership.presentation | membership.infrastructure
    membership.application
    membership.domain

[importlinter:contract:no-db-inside]
name = 内側の層は sqlite3 を import しない
type = forbidden
source_modules =
    membership.domain
    membership.application
forbidden_modules =
    sqlite3
---------
Contracts
---------

Analyzed 13 files, 9 dependencies.
----------------------------------

依存は外側から内側へだけ向かう KEPT
内側の層は sqlite3 を import しない KEPT

Contracts: 2 kept, 0 broken.

register_member.py に from membership.infrastructure.sqlite_member_repository import SqliteMemberRepository を足すと、2つの決まりの両方で検出されます。

---------
Contracts
---------

Analyzed 13 files, 10 dependencies.
-----------------------------------

依存は外側から内側へだけ向かう BROKEN
内側の層は sqlite3 を import しない BROKEN

Contracts: 0 kept, 2 broken.

----------------
Broken contracts
----------------

依存は外側から内側へだけ向かう
---------------

membership.application is not allowed to import membership.infrastructure:

- membership.application.register_member -> 
membership.infrastructure.sqlite_member_repository (l.3)

内側の層は sqlite3 を import しない
--------------------------

membership.application is not allowed to import sqlite3:

-   membership.application.register_member -> 
membership.infrastructure.sqlite_member_repository (l.3)
    membership.infrastructure.sqlite_member_repository -> sqlite3 (l.2)

2つ目の検出では、sqlite3 に届くまでの import の経路が1段ずつ表示されます。直接 import していなくても、間接的にたどり着けば検出されるということです。

pyproject.toml に書く場合は、公式ドキュメントに次の形の例があります(mypackage の部分を自分のパッケージ名に変えます)。

[[tool.importlinter.contracts]]
name = "My layers contract"
type = "layers"
layers = [
    "mypackage.high",
    "mypackage.medium",
    "mypackage.low",
]

どちらを選ぶか:判断表

条件 選ぶもの
小規模で、画面とテーブルがほぼ1対1の CRUD 中心 3層レイヤード
業務ルールが少なく、入力チェックと保存が主な処理 3層レイヤード
業務ルールが多く、条件の組み合わせをテストで確かめたい 依存を逆にする
テストでデータベースや外部 API を切り離したい 依存を逆にする
保存先や外部サービスを入れ替える予定がある 依存を逆にする
同じ業務ロジックを、画面・バッチ・API の複数の入口から使う 依存を逆にする(入口ごとにプレゼンテーション層を足す)
初めは小さく、成長に合わせて変えたい 3層で始め、業務ルールが増えた機能から順にインターフェースを挟む

3層から依存を逆にした構成へ移す手順

今回の2つの例の差分が、そのまま移行の手順になります。

手順 行うこと 例での対応
1 業務ロジックが呼んでいる保存用のメソッドを書き出し、インターフェースにする ports.py の MemberRepository
2 業務ロジックの中で具体的なクラスを作るのをやめ、引数で受け取る RegisterMember.__init__(self, repository)
3 入力チェックなど、保存を伴わないルールをドメイン層に移す Member.create
4 具体的なクラスを選んでつなぐ場所を1か所に集める main.py
5 向きの検査を設定に加える .importlinter

レイヤード・オニオン・ヘキサゴナル・クリーンの違い

観点 レイヤード(3層) オニオン ヘキサゴナル クリーン
図の形 上下に積んだ層 同心円 六角形と、その外側のアダプター 同心円
中心・最下部にあるもの データアクセス(最下部) ドメインモデル(中心) アプリケーション(内側) Entities(中心)
強調していること 役割ごとに層を分ける すべてのコードは中心側の層に依存でき、外側には依存できない 利用者・プログラム・自動テスト・バッチのどれからでも同じように動かせ、実行時の機器やデータベースから切り離して開発・テストできる 依存は内側にだけ向かう(依存性のルール)
外部との接点の呼び方 データアクセス層 インフラ(外側) ポートとアダプター(動かす側と動かされる側の2種類) Interface Adapters と Frameworks and Drivers
今回の例との対応 例1 例2 例2(ports.py がポート、SQLite の実装と CLI がアダプター) 例2

クリーンアーキテクチャの記事は、ヘキサゴナルやオニオンなどの方式は細部に違いがあるものの、目指すところは同じだと述べています。また、円の数は4つに決まっているわけではなく、いくつに分けても依存性のルールは常に当てはまる、とも説明しています。名前の違いよりも、「内側が外側を知らない」という1点を守れているかを確かめるのが近道です。

確かめて分かったこと

  1. 3層レイヤードでは、業務ロジックを import しただけで sqlite3 が読み込まれました。 依存を逆にした構成では読み込まれず、テストの中でもそれを確認できました。
  2. 画面の表示は2つの構成でまったく同じでした。 違いが表れるのは import 文と、具体的なクラスを作る場所です。
  3. ファイル数は 4 から 6 に増えました(__init__.py とテストを除く)。小さな機能では、この増加が負担になるかどうかが判断の材料になります。
  4. import-linter の layers は、層を飛び越す import(プレゼンテーション層からデータアクセス層へ)を検出しませんでした。 上から下への向きだけを見るためです。飛び越しも止めたい場合は、forbidden の決まりを別に足します。
  5. forbidden の検査は間接的な import もたどりました。 application から infrastructure を経由して sqlite3 に届く経路が、2行で表示されました。
  6. Python の Protocol は、実装クラスが継承を書かなくても使えました。 SqliteMemberRepository もテスト用の InMemoryMemberRepository も MemberRepository を継承していませんが、同じメソッドを持っていればそのまま渡せます。

よくある質問

Q. レイヤードアーキテクチャとは何ですか? A. プログラムを役割ごとの層に分け、層の間の依存を一方向にそろえる構成です。プレゼンテーション・ビジネスロジック・データアクセスの3層がよく使われます。

Q. レイヤードアーキテクチャとクリーンアーキテクチャの違いは? A. 依存の向きです。従来のレイヤードは業務ロジックがデータアクセス層に依存し、クリーンアーキテクチャはインターフェースを挟んで、データアクセスの側が業務ロジックに依存します。

Q. オニオンアーキテクチャとクリーンアーキテクチャの違いは? A. 層の名前と分け方が異なりますが、依存を内側へ向けるという決まりは同じです。コードの構成としては、この記事の例2がどちらにも当てはまります。

Q. 3層アーキテクチャのディレクトリ構成はどう分けますか? A. 層ごとにディレクトリを作り、presentation、business、data のように役割の名前を付けます。層の順序を設定ファイルに書き、検査ツールで向きを確かめると構成が保たれます。

Q. 小さなシステムでもクリーンアーキテクチャにしたほうがよいですか? A. CRUD 中心の小規模なシステムは3層で足ります。業務ルールが増えてきた機能から、インターフェースを挟む形に移していけます。

まとめ

  • レイヤードアーキテクチャは、役割ごとに層を分け、依存を一方向にそろえる構成です。
  • 4つの方式の違いは層の数ではなく依存の向きにあります。
  • 従来の3層は業務ロジックがデータアクセス層に依存し、オニオン・ヘキサゴナル・クリーンはインターフェースで向きを逆にします。
  • 依存を逆にすると、データベースなしで業務ルールをテストできます。代わりにファイル数は増えます。
  • 小規模な CRUD 中心なら3層、業務ルールが多くテストで DB を切り離したいなら依存を逆にします。
  • 層の向きは import-linter などのツールで検査し、自動チェックに組み込みます。

参考・出典

  • Robert C. Martin「The Clean Architecture」(2012-08-13): https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
  • Jeffrey Palermo「The Onion Architecture : part 1」(2008-07-29): https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/
  • Alistair Cockburn「Hexagonal architecture」: https://alistair.cockburn.us/hexagonal-architecture/
  • Import Linter Documentation「Layers」: https://import-linter.readthedocs.io/en/stable/contract_types/layers/

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「レイヤードアーキテクチャとは:依存関係とディレクトリ構成例・クリーンとの違い」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?