GakuNin RDMとDydraを連携したRDFメタデータ管理システムの開発
はじめに
本記事では、GakuNin RDM(Research Data Management)とDydra RDFデータベースを連携させた、研究データのメタデータ管理システムの開発について解説します。このシステムは、研究プロジェクトのファイル管理とDublin Coreメタデータの登録・検索を統合的に扱うことができます。
システム概要
アーキテクチャ
┌─────────────────┐
│ Next.js 14 │
│ (App Router) │
└────────┬────────┘
│
┌────┴────┐
│ │
┌───▼───┐ ┌──▼─────┐
│GakuNin│ │ Dydra │
│ RDM │ │ RDF │
│ API │ │ DB │
└───────┘ └────────┘
主要技術スタック:
- Next.js 14 (App Router)
- NextAuth.js (OAuth 2.0認証)
- Dydra (RDFデータベース)
- GakuNin RDM API
- SPARQL (クエリ言語)
1. GakuNin RDMとの連携
1.1 OAuth 2.0認証の実装
GakuNin RDMはOAuth 2.0による認証をサポートしています。NextAuth.jsを使用してこれを実装しました。
// src/app/api/auth/[...nextauth]/authOptions.ts
export const authOptions: NextAuthOptions = {
providers: [
{
id: "gakunin",
name: "GakuNin RDM",
type: "oauth",
clientId: process.env.GAKUNIN_CLIENT_ID,
clientSecret: process.env.GAKUNIN_CLIENT_SECRET,
authorization: {
url: "https://accounts.rdm.nii.ac.jp/oauth2/authorize",
params: {
scope: "osf.full_read osf.full_write",
response_type: "code",
},
},
token: "https://accounts.rdm.nii.ac.jp/oauth2/token",
userinfo: "https://api.rdm.nii.ac.jp/v2/users/me/",
},
],
};
1.2 トークンの自動リフレッシュ
長時間のセッションを維持するため、アクセストークンの自動リフレッシュ機能を実装しました。
async jwt({ token, account, user }) {
// 初回ログイン時
if (account) {
token.accessToken = account.access_token;
token.refreshToken = account.refresh_token;
token.expiresAt = account.expires_at;
}
// トークンの有効期限チェック(5分前にリフレッシュ)
if (token.expiresAt) {
const currentTime = Math.floor(Date.now() / 1000);
const shouldRefresh = currentTime >= token.expiresAt - 300;
if (shouldRefresh && token.refreshToken) {
// リフレッシュトークンで新しいアクセストークンを取得
const refreshedTokens = await refreshAccessToken(token.refreshToken);
return {
...token,
accessToken: refreshedTokens.access_token,
refreshToken: refreshedTokens.refresh_token ?? token.refreshToken,
expiresAt: Math.floor(Date.now() / 1000) + refreshedTokens.expires_in,
};
}
}
return token;
}
1.3 プロジェクトとファイルの取得
GakuNin RDM APIを通じて、ユーザーのプロジェクト一覧とファイルを取得します。
// プロジェクト一覧の取得
const response = await fetch(
"https://api.rdm.nii.ac.jp/v2/users/me/nodes/?filter[category]=project",
{
headers: {
Authorization: `Bearer ${session.accessToken}`,
},
}
);
// ストレージ内のファイル取得
const filesResponse = await fetch(
`https://api.rdm.nii.ac.jp/v2/nodes/${projectId}/files/${provider}/`,
{
headers: {
Authorization: `Bearer ${session.accessToken}`,
},
}
);
2. Dydra RDFデータベースの活用
2.1 非公開データの管理
Dydraは本来公開RDFデータベースですが、APIトークンを使用することで非公開データの管理が可能です。
環境変数の設定:
DYDRA_ACCOUNT=your-account
DYDRA_REPOSITORY=your-repository
DYDRA_API_TOKEN=your-secret-token
APIトークンを使用したクエリ:
const response = await fetch(
`https://dydra.com/${account}/${repository}/sparql`,
{
method: 'POST',
headers: {
'Accept': 'application/sparql-results+json',
'Authorization': `Bearer ${process.env.DYDRA_API_TOKEN}`,
},
body: new URLSearchParams({
query: sparqlQuery,
}),
}
);
2.2 Named Graphによるデータ分離
Named Graphを使用することで、プロジェクトごとにデータを論理的に分離します。
Named Graphの設計:
GakuNin RDM APIから得られるリソースURIをそのままNamed Graph URIとして使用することで、データの出所とグラフの対応を明確にしています。
統合グラフ: https://api.rdm.nii.ac.jp/v2/nodes/{projectId}/
プロジェクトに関連するすべてのデータ(メタデータ、SKOS主題、プロファイル)を単一のNamed Graphに統合することで、以下のメリットがあります:
- シンプルなクエリ: 複数グラフをUNIONで結合する必要がない
- 効率的な検索: メタデータとSKOS主題の横断検索が容易
- データの一貫性: すべてのデータが同じグラフ内で管理される
- 外部システムとの相互運用性: RDMのリソースURIとRDFグラフの直接的な対応
- 管理の簡素化: グラフURIの統一による運用負荷の軽減
例えば、メタデータのdc:subjectでSKOS概念を参照している場合、同一グラフ内なので直接JOINして主題ラベルを取得できます:
SELECT ?file ?title ?subjectLabel
FROM <https://api.rdm.nii.ac.jp/v2/nodes/{projectId}/>
WHERE {
?file dc:title ?title ;
dc:subject ?subject .
?subject skos:prefLabel ?subjectLabel .
}
メタデータ登録時のNamed Graph指定:
const graphUri = `https://api.rdm.nii.ac.jp/v2/nodes/${projectId}/`;
const insertQuery = `
PREFIX dc: <http://purl.org/dc/elements/1.1/>
PREFIX dcterms: <http://purl.org/dc/terms/>
INSERT DATA {
GRAPH <${graphUri}> {
<${resourceUri}> a dcterms:BibliographicResource ;
dc:title "${metadata.title}" ;
dc:creator "${metadata.creator}" ;
dc:description "${metadata.description}" ;
dc:subject <${metadata.subject}> .
}
}
`;
プロジェクト単位での検索:
const graphUri = `https://api.rdm.nii.ac.jp/v2/nodes/${projectId}/`;
const searchQuery = `
PREFIX dc: <http://purl.org/dc/elements/1.1/>
PREFIX dcterms: <http://purl.org/dc/terms/>
SELECT ?resource ?title ?creator ?description
FROM <${graphUri}>
WHERE {
?resource a dcterms:BibliographicResource ;
dc:title ?title ;
dc:creator ?creator .
OPTIONAL { ?resource dc:description ?description }
FILTER(CONTAINS(LCASE(?title), LCASE("${keyword}")))
}
`;
2.3 Dublin Coreメタデータスキーマ
Dublin Core 15要素を完全にサポートしています。
interface DublinCoreMetadata {
title: string; // dc:title
creator: string; // dc:creator
subject: string; // dc:subject
description: string; // dc:description
publisher?: string; // dc:publisher
contributor?: string; // dc:contributor
date?: string; // dc:date
type?: string; // dc:type
format?: string; // dc:format
identifier?: string; // dc:identifier
source?: string; // dc:source
language?: string; // dc:language
relation?: string; // dc:relation
coverage?: string; // dc:coverage
rights?: string; // dc:rights
}
3. メタデータの登録と検索
3.1 メタデータ登録フロー
1. GakuNin RDMからファイル情報を取得
2. ユーザーがDublin Coreメタデータを入力
3. RDFトリプルに変換
4. DydraにSPARQL UPDATEで登録(Named Graph使用)
登録API実装:
export async function POST(request: NextRequest) {
const session = await getServerSession(authOptions);
if (!session?.accessToken) {
return NextResponse.json({ error: "認証が必要です" }, { status: 401 });
}
const metadata = await request.json();
// GakuNin RDM APIのプロジェクトURIをNamed Graph URIとして使用
const graphUri = `https://api.rdm.nii.ac.jp/v2/nodes/${metadata.projectId}/`;
// SPARQL INSERTクエリを構築
const insertQuery = buildInsertQuery(metadata, graphUri);
// Dydraに登録
const response = await fetch(
`https://dydra.com/${account}/${repository}/sparql`,
{
method: 'POST',
headers: {
'Content-Type': 'application/sparql-update',
'Authorization': `Bearer ${process.env.DYDRA_API_TOKEN}`,
},
body: insertQuery,
}
);
return NextResponse.json({ success: true });
}
3.2 高度な検索機能
複数のDublin Coreフィールドを横断したキーワード検索を実装しています。
PREFIX dc: <http://purl.org/dc/elements/1.1/>
PREFIX dcterms: <http://purl.org/dc/terms/>
SELECT DISTINCT ?resource ?title ?creator ?subject ?description
FROM <https://api.rdm.nii.ac.jp/v2/nodes/{projectId}/>
WHERE {
?resource a dcterms:BibliographicResource .
OPTIONAL { ?resource dc:title ?title }
OPTIONAL { ?resource dc:creator ?creator }
OPTIONAL { ?resource dc:subject ?subject }
OPTIONAL { ?resource dc:description ?description }
# キーワードを複数フィールドで検索
FILTER(
CONTAINS(LCASE(?title), LCASE("keyword")) ||
CONTAINS(LCASE(?creator), LCASE("keyword")) ||
CONTAINS(LCASE(?subject), LCASE("keyword")) ||
CONTAINS(LCASE(?description), LCASE("keyword"))
)
}
ORDER BY ?title
4. SKOS主題階層管理
4.1 SKOS概念スキーマの実装
主題の階層構造を管理するため、SKOS(Simple Knowledge Organization System)を採用しました。
interface SKOSConcept {
uri: string;
prefLabel: string;
broader?: string; // 上位概念
narrower?: string[]; // 下位概念
}
SKOS登録のSPARQLクエリ:
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
INSERT DATA {
GRAPH <https://api.rdm.nii.ac.jp/v2/nodes/{projectId}/> {
<{conceptUri}> a skos:Concept ;
skos:prefLabel "{label}"@ja ;
skos:broader <{broaderUri}> .
}
}
4.2 階層構造の取得と主題ラベルの活用
統合グラフにより、メタデータと主題の関連を簡単にクエリできます:
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
PREFIX dc: <http://purl.org/dc/elements/1.1/>
SELECT ?concept ?label ?broader ?broaderLabel
FROM <https://api.rdm.nii.ac.jp/v2/nodes/{projectId}/>
WHERE {
?concept a skos:Concept ;
skos:prefLabel ?label .
OPTIONAL {
?concept skos:broader ?broader .
?broader skos:prefLabel ?broaderLabel .
}
}
ORDER BY ?label
5. プロジェクトRDFエクスポート機能
5.1 プロジェクト全体のRDFデータ取得
統合グラフにより、すべてのデータ(メタデータ、SKOS主題、プロファイル)を単一のCONSTRUCTクエリでエクスポートできます。
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const projectId = params.id;
const graphUri = `https://api.rdm.nii.ac.jp/v2/nodes/${projectId}/`;
// CONSTRUCT クエリでRDFグラフを取得(UNIONは不要)
const query = `
PREFIX dc: <http://purl.org/dc/elements/1.1/>
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
PREFIX sh: <http://www.w3.org/ns/shacl#>
CONSTRUCT {
?s ?p ?o
}
WHERE {
GRAPH <${graphUri}> {
?s ?p ?o
}
}
`;
const response = await fetch(
`https://dydra.com/${account}/${repository}/sparql`,
{
method: 'POST',
headers: {
'Accept': 'text/turtle',
'Authorization': `Bearer ${process.env.DYDRA_API_TOKEN}`,
},
body: new URLSearchParams({ query }),
}
);
const rdfData = await response.text();
return new NextResponse(rdfData, {
headers: {
'Content-Type': 'text/turtle; charset=utf-8',
'Content-Disposition': `attachment; filename="project_${projectId}.ttl"`,
},
});
}
6. UIコンポーネントの設計
6.1 メタデータエディタ
Dublin Core 15要素すべてを編集可能なフォームコンポーネントを実装しています。
const MetadataEditor = ({ fileId, projectId }: Props) => {
const [metadata, setMetadata] = useState<DublinCoreMetadata>({
title: '',
creator: '',
subject: '',
description: '',
// ... 他のフィールド
});
const handleSave = async () => {
const response = await fetch('/api/metadata/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...metadata,
fileId,
projectId,
}),
});
if (response.ok) {
alert('メタデータを登録しました');
}
};
return (
<div className="space-y-4">
{/* 15個のDublin Core入力フィールド */}
<input
value={metadata.title}
onChange={(e) => setMetadata({ ...metadata, title: e.target.value })}
placeholder="タイトル"
/>
{/* ... */}
<button onClick={handleSave}>保存</button>
</div>
);
};
6.2 検索インターフェース
キーワード検索とフィールド別検索の両方をサポートしています。
const SearchInterface = ({ projectId }: Props) => {
const [keyword, setKeyword] = useState('');
const [fieldSearch, setFieldSearch] = useState({
title: '',
creator: '',
subject: '',
// ...
});
const handleSearch = async () => {
const response = await fetch(
`/api/projects/${projectId}/search`,
{
method: 'POST',
body: JSON.stringify({ keyword, ...fieldSearch }),
}
);
const results = await response.json();
setResults(results);
};
return (
<div>
<input
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
placeholder="キーワードで検索"
/>
{/* フィールド別検索フォーム */}
<button onClick={handleSearch}>検索</button>
</div>
);
};
7. セキュリティとパフォーマンス
7.1 認証とアクセス制御
- すべてのAPIエンドポイントでセッション検証
- アクセストークンの自動リフレッシュ
- Named Graphによるプロジェクト単位のデータ分離
7.2 パフォーマンス最適化
- Next.js App Routerによるサーバーサイドレンダリング
- SPARQL クエリの最適化(OPTIONAL句の活用)
- トークンリフレッシュの先行実行(有効期限5分前)
8. デプロイとインフラ
8.1 Vercelへのデプロイ
# 環境変数の設定
NEXT_PUBLIC_SITE_URL=https://your-domain.com
NEXTAUTH_URL=https://your-domain.com
NEXTAUTH_SECRET=your-secret
GAKUNIN_CLIENT_ID=your-client-id
GAKUNIN_CLIENT_SECRET=your-client-secret
DYDRA_ACCOUNT=your-account
DYDRA_REPOSITORY=your-repository
DYDRA_API_TOKEN=your-api-token
# デプロイ
vercel --prod
8.2 GakuNin RDM OAuth設定
リダイレクトURIの登録:
https://your-domain.com/api/auth/callback/gakunin
まとめ
本システムでは、以下の技術的課題を解決しました:
- OAuth 2.0認証の実装 - GakuNin RDMとの安全な連携
- トークン自動リフレッシュ - 長時間セッションの維持
- Named Graphによるデータ分離 - プロジェクト単位の管理
- 非公開RDFデータの管理 - APIトークンによるアクセス制御
- Dublin Core完全対応 - 標準メタデータスキーマの実装
- SKOS主題階層管理 - 構造化された主題分類
- SPARQL検索 - 柔軟なメタデータ検索
このシステムは、研究データ管理における実用的なソリューションとして、オープンサイエンスの推進に貢献します。
参考リンク
- GakuNin RDM API Documentation
- Dydra Documentation
- Dublin Core Metadata Initiative
- SKOS Simple Knowledge Organization System
- SPARQL 1.1 Query Language
ソースコード
完全なソースコードは以下のリポジトリで公開しています:
Discussion