Metadata Mapping (Распределение на основе метаданных)

Паттерн проектирования Metadata Mapping

Паттерн проектирования Metadata Mapping

Описание Metadata Mapping

Хранит данные об объектно-реляционном распределении в метаданных

В большинстве случаев, код, управляющий объектно-реляционным распределением описывает, как поля в БД соотносятся с полями в объектах. Такой код получается сильно повторяющимся и скучным. Паттерн Metadata Mapping позволяет разработчикам определять правила распределения (маппинга) в простой табличной форме, которая может быть обработана общим кодом, который получает данные о правилах записи и чтения данных из БД.

Примеры реализации

// Metadata Mapping Pattern in JavaScript
class MetadataMapping {
    constructor() {
        this.mappings = new Map();
    }
    
    addMapping(className, mapping) {
        this.mappings.set(className, mapping);
        console.log(`Mapping added for class: ${className}`);
    }
    
    getMapping(className) {
        return this.mappings.get(className);
    }
    
    mapObjectToRecord(obj, className) {
        const mapping = this.getMapping(className);
        if (!mapping) {
            throw new Error(`No mapping found for class: ${className}`);
        }
        
        const record = {};
        for (const [fieldName, columnName] of Object.entries(mapping.fields)) {
            record[columnName] = obj[fieldName];
        }
        
        console.log(`Mapped ${className} object to record:`, record);
        return record;
    }
    
    mapRecordToObject(record, className) {
        const mapping = this.getMapping(className);
        if (!mapping) {
            throw new Error(`No mapping found for class: ${className}`);
        }
        
        const obj = {};
        for (const [fieldName, columnName] of Object.entries(mapping.fields)) {
            obj[fieldName] = record[columnName];
        }
        
        console.log(`Mapped record to ${className} object:`, obj);
        return obj;
    }
}

// Define metadata mappings
const userMapping = {
    tableName: 'users',
    fields: {
        id: 'user_id',
        name: 'full_name',
        email: 'email_address',
        createdAt: 'created_at'
    }
};

const productMapping = {
    tableName: 'products',
    fields: {
        id: 'product_id',
        name: 'product_name',
        price: 'unit_price',
        categoryId: 'category_id'
    }
};

// Usage
const metadataMapping = new MetadataMapping();

// Register mappings
metadataMapping.addMapping('User', userMapping);
metadataMapping.addMapping('Product', productMapping);

// Example objects
const user = {
    id: 1,
    name: 'John Doe',
    email: 'john@example.com',
    createdAt: new Date()
};

const product = {
    id: 101,
    name: 'Laptop',
    price: 999.99,
    categoryId: 5
};

// Map objects to database records
const userRecord = metadataMapping.mapObjectToRecord(user, 'User');
const productRecord = metadataMapping.mapObjectToRecord(product, 'Product');

// Map database records back to objects
const userFromRecord = metadataMapping.mapRecordToObject(userRecord, 'User');
const productFromRecord = metadataMapping.mapRecordToObject(productRecord, 'Product');

console.log('User from record:', userFromRecord);
console.log('Product from record:', productFromRecord);
<?php
// Metadata Mapping Pattern in PHP
class MetadataMapping {
    private $mappings = [];
    
    public function addMapping($className, $mapping) {
        $this->mappings[$className] = $mapping;
        echo "Mapping added for class: $className\n";
    }
    
    public function getMapping($className) {
        return $this->mappings[$className] ?? null;
    }
    
    public function mapObjectToRecord($obj, $className) {
        $mapping = $this->getMapping($className);
        if (!$mapping) {
            throw new Exception("No mapping found for class: $className");
        }
        
        $record = [];
        foreach ($mapping['fields'] as $fieldName => $columnName) {
            $record[$columnName] = $obj[$fieldName];
        }
        
        echo "Mapped $className object to record: " . json_encode($record) . "\n";
        return $record;
    }
    
    public function mapRecordToObject($record, $className) {
        $mapping = $this->getMapping($className);
        if (!$mapping) {
            throw new Exception("No mapping found for class: $className");
        }
        
        $obj = [];
        foreach ($mapping['fields'] as $fieldName => $columnName) {
            $obj[$fieldName] = $record[$columnName];
        }
        
        echo "Mapped record to $className object: " . json_encode($obj) . "\n";
        return $obj;
    }
}

// Define metadata mappings
$userMapping = [
    'tableName' => 'users',
    'fields' => [
        'id' => 'user_id',
        'name' => 'full_name',
        'email' => 'email_address',
        'createdAt' => 'created_at'
    ]
];

$productMapping = [
    'tableName' => 'products',
    'fields' => [
        'id' => 'product_id',
        'name' => 'product_name',
        'price' => 'unit_price',
        'categoryId' => 'category_id'
    ]
];

// Usage
$metadataMapping = new MetadataMapping();

// Register mappings
$metadataMapping->addMapping('User', $userMapping);
$metadataMapping->addMapping('Product', $productMapping);

// Example objects
$user = [
    'id' => 1,
    'name' => 'John Doe',
    'email' => 'john@example.com',
    'createdAt' => date('Y-m-d H:i:s')
];

$product = [
    'id' => 101,
    'name' => 'Laptop',
    'price' => 999.99,
    'categoryId' => 5
];

// Map objects to database records
$userRecord = $metadataMapping->mapObjectToRecord($user, 'User');
$productRecord = $metadataMapping->mapObjectToRecord($product, 'Product');

// Map database records back to objects
$userFromRecord = $metadataMapping->mapRecordToObject($userRecord, 'User');
$productFromRecord = $metadataMapping->mapRecordToObject($productRecord, 'Product');

echo "User from record: " . json_encode($userFromRecord) . "\n";
echo "Product from record: " . json_encode($productFromRecord) . "\n";
?>
// Metadata Mapping Pattern in Go
package main

import (
    "fmt"
    "time"
)

type FieldMapping struct {
    TableName string
    Fields    map[string]string
}

type MetadataMapping struct {
    mappings map[string]FieldMapping
}

func NewMetadataMapping() *MetadataMapping {
    return &MetadataMapping{
        mappings: make(map[string]FieldMapping),
    }
}

func (mm *MetadataMapping) AddMapping(className string, mapping FieldMapping) {
    mm.mappings[className] = mapping
    fmt.Printf("Mapping added for class: %s\n", className)
}

func (mm *MetadataMapping) GetMapping(className string) (FieldMapping, bool) {
    mapping, exists := mm.mappings[className]
    return mapping, exists
}

func (mm *MetadataMapping) MapObjectToRecord(obj map[string]interface{}, className string) (map[string]interface{}, error) {
    mapping, exists := mm.GetMapping(className)
    if !exists {
        return nil, fmt.Errorf("no mapping found for class: %s", className)
    }
    
    record := make(map[string]interface{})
    for fieldName, columnName := range mapping.Fields {
        if value, exists := obj[fieldName]; exists {
            record[columnName] = value
        }
    }
    
    fmt.Printf("Mapped %s object to record: %+v\n", className, record)
    return record, nil
}

func (mm *MetadataMapping) MapRecordToObject(record map[string]interface{}, className string) (map[string]interface{}, error) {
    mapping, exists := mm.GetMapping(className)
    if !exists {
        return nil, fmt.Errorf("no mapping found for class: %s", className)
    }
    
    obj := make(map[string]interface{})
    for fieldName, columnName := range mapping.Fields {
        if value, exists := record[columnName]; exists {
            obj[fieldName] = value
        }
    }
    
    fmt.Printf("Mapped record to %s object: %+v\n", className, obj)
    return obj, nil
}

func main() {
    // Define metadata mappings
    userMapping := FieldMapping{
        TableName: "users",
        Fields: map[string]string{
            "id":        "user_id",
            "name":      "full_name",
            "email":     "email_address",
            "createdAt": "created_at",
        },
    }
    
    productMapping := FieldMapping{
        TableName: "products",
        Fields: map[string]string{
            "id":         "product_id",
            "name":       "product_name",
            "price":      "unit_price",
            "categoryId": "category_id",
        },
    }
    
    // Usage
    metadataMapping := NewMetadataMapping()
    
    // Register mappings
    metadataMapping.AddMapping("User", userMapping)
    metadataMapping.AddMapping("Product", productMapping)
    
    // Example objects
    user := map[string]interface{}{
        "id":        1,
        "name":      "John Doe",
        "email":     "john@example.com",
        "createdAt": time.Now(),
    }
    
    product := map[string]interface{}{
        "id":         101,
        "name":       "Laptop",
        "price":      999.99,
        "categoryId": 5,
    }
    
    // Map objects to database records
    userRecord, err := metadataMapping.MapObjectToRecord(user, "User")
    if err != nil {
        fmt.Printf("Error mapping user: %v\n", err)
        return
    }
    
    productRecord, err := metadataMapping.MapObjectToRecord(product, "Product")
    if err != nil {
        fmt.Printf("Error mapping product: %v\n", err)
        return
    }
    
    // Map database records back to objects
    userFromRecord, err := metadataMapping.MapRecordToObject(userRecord, "User")
    if err != nil {
        fmt.Printf("Error mapping user from record: %v\n", err)
        return
    }
    
    productFromRecord, err := metadataMapping.MapRecordToObject(productRecord, "Product")
    if err != nil {
        fmt.Printf("Error mapping product from record: %v\n", err)
        return
    }
    
    fmt.Printf("User from record: %+v\n", userFromRecord)
    fmt.Printf("Product from record: %+v\n", productFromRecord)
}

Использована иллюстрация с сайта Мартина Фаулера.

Источник