Spring Boot Avro ඒකාබද්ධ කිරීම: දත්ත Serialize කිරීම සහ Schema Evolution | SC Guide

Spring Boot Avro ඒකාබද්ධ කිරීම: දත්ත Serialize කිරීම සහ Schema Evolution | SC Guide

ආයුබෝවන් හැමෝටම! කොහොමද ඉතින්? අද අපි කතා කරන්න යන්නේ Spring Boot project වල data serialization කරනකොට අපිට ගොඩක් වැදගත් වෙන, ඒ වගේම schema evolution කියන සංකල්පයට නියම විසඳුමක් දෙන, Apache Avro ගැනයි. Programming කරන ඔයාලා හැමෝම දන්නවානේ, distributed systems, microservices වගේ දේවල් වලදී data එක serialize කරලා ඒක network එක හරහා යවලා, අනිත් පැත්තේදී deserialize කරන එක කොච්චර වැදගත්ද කියලා. JSON, XML වගේ formats පාවිච්චි කරත්, data විශාල ප්‍රමාණයක් handle කරනකොට, ඒ වගේම data format එක වෙනස් වෙනකොට එන ප්‍රශ්න වලට Avro කියන්නේ නියම උත්තරයක්.

අපි මේ guide එකෙන් බලමු Spring Boot project එකක් ඇතුලේ Avro integrate කරන්නේ කොහොමද, data serialize කරන්නේ සහ deserialize කරන්නේ කොහොමද, ඒ වගේම Avro වල තියෙන සුපිරිම feature එකක් වෙන schema evolution එකත් එක්ක වැඩ කරන්නේ කොහොමද කියලා. එහෙනම් වැඩේට බහිමු!

Apache Avro කියන්නේ මොකක්ද?

සරලවම කියනවා නම්, Avro කියන්නේ Apache Software Foundation එකෙන් හදපු remote procedure call (RPC) සහ data serialization framework එකක්. මේකේ විශේෂත්වය තමයි schema-driven වෙන එක. ඒ කියන්නේ, අපි data එකේ structure එක schema එකකින් define කරනවා. මේ schema එක JSON format එකෙන් තියෙන්නේ. Avro මේ schema එක පාවිච්චි කරලා data එක binary format එකකට serialize කරනවා. ඒ නිසා data එක ගොඩක් compact වෙනවා වගේම, serialize / deserialize කරන speed එකත් වැඩියි.

JSON/XML වලට වඩා Avro හොඳ ඇයි?

  • Compact Data Format: Avro binary format එකක් පාවිච්චි කරන නිසා, JSON වගේ text-based formats වලට වඩා data size එක ගොඩක් අඩුයි. මේක network bandwidth එක ඉතුරු කරගන්න වගේම, storage space එකත් අඩු කරගන්න උදව් වෙනවා.
  • Fast Serialization/Deserialization: Binary format නිසා data read/write කරන වේගය වැඩියි. High-throughput systems වලට මේක හරිම වැදගත්.
  • Schema Evolution: මේක තමයි Avro වල තියෙන ලොකුම වාසිය. Data schema එක කාලෙන් කාලෙට වෙනස් වෙනවා නේ. අලුත් fields එකතු වෙන්න, පරණ fields අයින් වෙන්න පුලුවන්. Avro වලදී, අපි schema එක වෙනස් කරාට පරණ client applications වලටත් අලුත් client applications වලටත් compatibility තියාගෙන වැඩ කරන්න පුලුවන්. ඒ කියන්නේ, අලුත් client එකකට පරණ data එකක් කියවන්න පුලුවන් වගේම, පරණ client එකකට අලුත් data එකක් කියවන්නත් පුලුවන් (නිසි ලෙස schema define කරලා තියනවා නම්).
  • Language Agnostic: Avro schema එක JSON format එකෙන් නිසා, Java, Python, C#, C++, Ruby වගේ විවිධ programming languages වලදී පාවිච්චි කරන්න පුලුවන්.

Spring Boot එකට Avro ගලපගමු!

හරි, එහෙනම් අපි බලමු Spring Boot project එකකට Avro integrate කරගන්නේ කොහොමද කියලා. මුලින්ම අපිට පුලුවන් Spring Initializr එකෙන් අලුත් project එකක් හදාගන්න. ඊට පස්සේ pom.xml එකට Avro dependencies ටිකයි, Avro Maven plugin එකයි එකතු කරන්න ඕනේ. මේ plugin එකෙන් තමයි අපි දෙන Avro schema (.avsc) files වලින් Java classes auto-generate කරගන්නේ.

Maven Dependencies

ඔයාලගේ pom.xml එකේ <dependencies> section එකට මේ ටික එකතු කරන්න.

<dependency>
    <groupId>org.apache.avro</groupId>
    <artifactId>avro</artifactId>
    <version>1.11.1</version> <!-- නවතම version එක බලන්න -->
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>

Avro Schema File (.avsc)

දැන් අපි අපේ data එකේ structure එක define කරන Avro schema file එකක් හදාගමු. අපි මෙතනදී simple User object එකක් හදමු. src/main/resources/avro/user.avsc කියලා file එකක් හදාගෙන මේ content එක එකතු කරන්න:

{
  "namespace": "com.example.avro",
  "type": "record",
  "name": "User",
  "fields": [
    {"name": "firstName", "type": "string"},
    {"name": "lastName", "type": "string"},
    {"name": "age", "type": "int"}
  ]
}

මේකෙන් අපි firstName (string), lastName (string), සහ age (int) කියන fields තුන තියෙන User කියන record එක define කරනවා. namespace එක කියන්නේ Java package name එක වගේ එකක්. මේකෙන් තමයි generate වෙන class එකේ package එක තීරණය වෙන්නේ.

Avro Code Generation

දැන් අපි Avro schema එකෙන් Java classes auto-generate කරන්න Maven plugin එක configure කරමු. pom.xml එකේ <build> section එකට මේ plugin configuration එක එකතු කරන්න:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.avro</groupId>
            <artifactId>avro-maven-plugin</artifactId>
            <version>1.11.1</version> <!-- Avro dependency version එකම දෙන්න -->
            <executions>
                <execution>
                    <id>schemas</id>
                    <phase>generate-sources</phase>
                    <goals>
                        <goal>schema</goal>
                    </goals>
                    <configuration>
                        <sourceDirectory>${project.basedir}/src/main/resources/avro/</sourceDirectory>
                        <outputDirectory>${project.basedir}/src/main/java/</outputDirectory>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>build-helper-maven-plugin</artifactId>
            <version>3.5.0</version>
            <executions>
                <execution>
                    <id>add-source</id>
                    <phase>generate-sources</phase>
                    <goals>
                        <goal>add-source</goal>
                    </goals>
                    <configuration>
                        <sources>
                            <source>${project.basedir}/src/main/java/</source>
                        </sources>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

මේ avro-maven-plugin එක generate-sources phase එකේදී src/main/resources/avro/ folder එකේ තියෙන .avsc files අරගෙන, src/main/java/ folder එක ඇතුලේ අදාල Java classes generate කරනවා. build-helper-maven-plugin එකෙන් generate වෙන source folder එක project classpath එකට add කරනවා, ඒ නිසා IDE එකට generated classes identify කරගන්න පුලුවන්.

දැන් mvn clean install (හෝ mvn generate-sources) run කරාම src/main/java/com/example/avro/User.java කියන file එක generate වෙලා තියෙන්න ඕනේ. නියමයි නේද!

Data Serialization සහ Deserialization

දැන් අපි බලමු Avro generated classes පාවිච්චි කරලා data serialize කරන්නේ සහ deserialize කරන්නේ කොහොමද කියලා. මේක හරිම සරලයි.

Producing Avro Messages (Sending data)

අපි User object එකක් හදලා ඒක Avro binary format එකට serialize කරමු. මේක අපි ByteArrayOutputStream එකකට ලියලා බලමු.

package com.example.avro; // generate වුනු package එක

import org.apache.avro.io.DatumWriter;
import org.apache.avro.io.Encoder;
import org.apache.avro.io.EncoderFactory;
import org.apache.avro.specific.SpecificDatumWriter;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;

public class AvroSerializer {

    public byte[] serializeUser(User user) throws IOException {
        DatumWriter<User> userDatumWriter = new SpecificDatumWriter<>(User.class);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        Encoder binaryEncoder = EncoderFactory.get().binaryEncoder(outputStream, null);

        userDatumWriter.write(user, binaryEncoder);
        binaryEncoder.flush();
        outputStream.close();

        return outputStream.toByteArray();
    }

    public static void main(String[] args) throws IOException {
        AvroSerializer serializer = new AvroSerializer();

        User user = User.newBuilder()
                .setFirstName("Sunil")
                .setLastName("Perera")
                .setAge(30)
                .build();

        byte[] serializedData = serializer.serializeUser(user);
        System.out.println("Serialized User data (bytes): " + Arrays.toString(serializedData));
        System.out.println("Serialized data length: " + serializedData.length + " bytes");
    }
}

මෙතනදී අපි SpecificDatumWriter එකක් පාවිච්චි කරන්නේ Avro generated classes එක්ක වැඩ කරන්න. EncoderFactory.get().binaryEncoder() එකෙන් අපිට binary format එකට serialize කරන්න පුලුවන් Encoder එකක් හම්බවෙනවා. මේක Kafka වගේ message brokers වලට data publish කරනකොට ගොඩක් වැදගත්.

Consuming Avro Messages (Receiving data)

serialize කරපු Avro data එකක් නැවත original Java object එකට deserialize කරන්නේ කොහොමද කියලා බලමු. ඒකත් serialize කරනවා වගේම සරලයි.

package com.example.avro;

import org.apache.avro.io.DatumReader;
import org.apache.avro.io.Decoder;
import org.apache.avro.io.DecoderFactory;
import org.apache.avro.specific.SpecificDatumReader;

import java.io.ByteArrayInputStream;
import java.io.IOException;

public class AvroDeserializer {

    public User deserializeUser(byte[] data) throws IOException {
        DatumReader<User> userDatumReader = new SpecificDatumReader<>(User.class);
        ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
        Decoder binaryDecoder = DecoderFactory.get().binaryDecoder(inputStream, null);

        return userDatumReader.read(null, binaryDecoder);
    }

    public static void main(String[] args) throws IOException {
        // පෙර serialize කරගත් data byte array එක ගන්න
        AvroSerializer serializer = new AvroSerializer();
        User originalUser = User.newBuilder()
                .setFirstName("Sunil")
                .setLastName("Perera")
                .setAge(30)
                .build();
        byte[] serializedData = serializer.serializeUser(originalUser);

        AvroDeserializer deserializer = new AvroDeserializer();
        User deserializedUser = deserializer.deserializeUser(serializedData);

        System.out.println("Deserialized User: " + deserializedUser);
        System.out.println("First Name: " + deserializedUser.getFirstName());
        System.out.println("Last Name: " + deserializedUser.getLastName());
        System.out.println("Age: " + deserializedUser.getAge());
    }
}

මෙතනදී අපි SpecificDatumReader එකක් පාවිච්චි කරනවා Avro generated classes read කරන්න. DecoderFactory.get().binaryDecoder() එකෙන් අපිට binary data read කරන්න පුලුවන් Decoder එකක් හම්බවෙනවා.

මේ main methods run කරලා බලන්න. ඔයාලට පෙනෙයි data serialize වෙලා, ආයෙත් deserialize වෙන හැටි.

Schema Evolution - අලුත් වෙනස්කම් එක්ක වැඩ කරමු

මේක තමයි Avro වල තියෙන ලොකුම වාසිය. අපි හිතමු ඔයාලගේ production system එකක data format එකට අලුත් field එකක් එකතු කරන්න ඕනේ කියලා. සාමාන්‍යයෙන් JSON වගේ එකක නම් මේ වගේ වෙලාවට backwards compatibility නැති වෙන්න පුළුවන්. ඒත් Avro වලදී, අපි schema එක පොඩ්ඩක් modify කරලා, default values දාලා, පරණ client ලාටත්, අලුත් client ලාටත් එකම data එක handle කරන්න පුලුවන් විදියට හදාගන්න පුලුවන්. මේක තමයි schema evolution කියන්නේ.

Backward Compatibility (පරණ producer, අලුත් consumer)

ඔයාලගේ producer (data යවන system එක) පරණ schema එක පාවිච්චි කරනවා, හැබැයි consumer (data ගන්න system එක) අලුත් schema එකට update වෙලා කියලා හිතමු. Avro වලදී, අලුත් schema එකට අලුත් fields add කරනකොට, ඒ field එකට default value එකක් දාන්න පුලුවන් නම්, పරණ producer එකෙන් එවන data එක අලුත් consumer එකට කිසි ප්‍රශ්නයක් නැතුව read කරන්න පුලුවන්. අලුත් field එකට default value එක Auto fill වෙනවා.

අපි user.avsc file එකට අලුතින් email කියන field එකක් එකතු කරමු. ඒකට default value එකක් දෙමු.

src/main/resources/avro/user.avsc (Updated):

{
  "namespace": "com.example.avro",
  "type": "record",
  "name": "User",
  "fields": [
    {"name": "firstName", "type": "string"},
    {"name": "lastName", "type": "string"},
    {"name": "age", "type": "int"},
    {"name": "email", "type": "string", "default": "[email protected]"}
  ]
}

දැන් mvn clean install කරලා අලුත් User.java file එක generate කරගන්න. දැන් අපි පරණ schema එකෙන් serialize කරපු data එක (email field එක නැති) අලුත් schema එකෙන් deserialize කරලා බලමු.

// AvroDeserializer class එකේ main method එකට මේක එකතු කරන්න
// පෙර serialize කරගත් data byte array එක ගන්න (email field එක නැති)
// මේක කලින් serialize කරපු AvroSerializer class එකෙන් ගත්ත data එකක් ලෙස සලකන්න.
// අපි මෙතනට hardcode කරලා දැම්මොත්, default value එක check කරන්න පුළුවන්.
// උදාහරණයක් ලෙස, AvroSerializer main method එකෙන් generate වුනු byte array එක මෙතන දාන්න.

// මෙහි පහත code block එකේ `oldSerializedData` වෙනුවට
// email field එක නැතුව serialize කරපු byte array එක දාන්න.
byte[] oldSerializedData = serializer.serializeUser(User.newBuilder()
        .setFirstName("Nimal")
        .setLastName("Silva")
        .setAge(45)
        .build()); // මේක තමයි පරණ schema එකෙන් serialize වුනු data එක

User deserializedOldUser = deserializer.deserializeUser(oldSerializedData);
System.out.println("\nDeserialized Old User (with new schema): " + deserializedOldUser);
System.out.println("First Name: " + deserializedOldUser.getFirstName());
System.out.println("Last Name: " + deserializedOldUser.getLastName());
System.out.println("Age: " + deserializedOldUser.getAge());
System.out.println("Email (default): " + deserializedOldUser.getEmail()); // මෙතන default value එක එන්න ඕනේ

ඔයාලට පෙනෙයි email field එකට [email protected] කියන default value එක Auto fill වෙලා තියෙනවා. නියමයි නේද? මේක තමයි Avro වල Schema Evolution වල බලය.

Forward Compatibility (අලුත් producer, පරණ consumer)

මේක ටිකක් සංකීර්ණයි. producer එක අලුත් schema එක පාවිච්චි කරනවා, හැබැයි consumer එක පරණ schema එකට update වෙලා නෑ කියලා හිතමු. මේ වගේ වෙලාවට, අලුත් producer එකෙන් යවන data එකේ අලුත් field එකක් තියෙනවා නම්, පරණ consumer එකට ඒක read කරන්න බැරි වෙන්න පුලුවන්, මොකද පරණ schema එකේ ඒ field එක define කරලා නැති නිසා. මේක විසඳන්න නම්, අලුත් field එකට default value එකක් තියෙන්න ඕනේ. ඒ වගේම, පරණ consumer එකේ schema එක, අලුත් producer එකේ schema එකේ subset එකක් වෙන්න ඕනේ. ඒ කියන්නේ, අලුත් field එක පරණ consumer එකට ignore කරන්න පුලුවන් වෙන්න ඕනේ.

Avro වලදී fields add කරන එක සහ Optional fields (Union Types ["null", "type"] එක්ක default null) පාවිච්චි කරන එක මේකට උදව් වෙනවා.

නිගමනය

ඉතින්, අද අපි Spring Boot project එකක Apache Avro පාවිච්චි කරලා data serialization සහ schema evolution කියන සංකල්ප ගැන කතා කළා. Distributed systems, microservices වගේ scalable applications හදනකොට Avro කියන්නේ නැතුවම බැරි tool එකක්. ඒ වගේම, performance, data size optimization, සහ future-proofing වලට Avro දෙන support එකත් අති විශාලයි.

Kafka වගේ Event-driven architectures වලදී Avro integrate කරන එකෙන් ගොඩක් වාසි ගන්න පුලුවන්, මොකද Kafka messages Binary format එකෙන් තියන නිසා space save වෙනවා වගේම, schema registry එකක් එක්ක Avro පාවිච්චි කරනකොට schema evolution එක manage කරන එක හරිම පහසුයි. ඒ ගැනත් අපි ඉදිරියේදී කතා කරමු.

මේ Concepts ඔයාලගේ project වලටත් integrate කරලා බලන්න. මොනවා හරි ප්‍රශ්න තියෙනවා නම්, අදහස් තියෙනවා නම් පහලින් Comment section එකේ දාගෙන යන්න. ඒ වගේම මේ article එක Share කරන්නත් අමතක කරන්න එපා. තවත් මේ වගේ වැදගත් article එකකින් හමුවෙමු! Happy Coding!