The Curator-Project – Implementing the data structure

As this service ist the first of several I’ll invest a little bit more in a generic approach. Some of the generics and abstract classes might seem a little bit over the top.

I’ll as well use Lombok even it’s not the considered for some functionality on the entities. Feel free to de-lombok the functions. For now it will spare me some boilerplate.

Metadata

All of entities will implement the interface MetaData.

public interface MetaData<T extends Serializable> {
  T getId();
  Long getVersion();
  String getCreatedBy();

  ZonedDateTime getCreatedDate();
  String getLastModifiedBy();

  ZonedDateTime getLastModifiedDate();
}

To make this a little bit more convenient the abstract class AbstractEntity offers all functions so that this methods can be called via extends.

@AllArgsConstructor
@Data
@EntityListeners(AuditingEntityListener.class)
@EqualsAndHashCode(of = {"lastModifiedBy", "lastModifiedDate", "name", "id"}, callSuper = false)
@MappedSuperclass
@NoArgsConstructor
@SuperBuilder
@ToString
@Validated
public abstract class AbstractEntity<T extends Serializable> implements MetaData<T>, View<T> {

  @Id
  @GeneratedValue(strategy = GenerationType.UUID)
  @Column(name = "ID", nullable = false, unique = true)
  private T id;

  @NotBlank
  @Size(min = 3, max = 50)
  @Column(name = "NAME", length = 50)
  private String name;

  @Version
  @Column(name = "VERSION")
  private Long version;

  @CreatedBy
  @Column(name = "CREATED_BY")
  private String createdBy;

  @CreatedDate
  @Column(name = "CREATED_DATE")
  private ZonedDateTime createdDate;

  @LastModifiedBy
  @Column(name = "LAST_MODIFIED_BY")
  private String lastModifiedBy;

  @LastModifiedDate
  @Column(name = "LAST_MODIFIED_DATE")
  private ZonedDateTime lastModifiedDate;

  @Builder.Default
  @Convert(converter = MapStringConverter.class)
  @Column(name = "attributes", nullable = false)
  private Map<String, String> attributes = new HashMap<>();
}

At this point the strategy of the id-generation is generic as a type but the annotation doesn’t support this. This a known Bug for the moment.

UserEntity

All entities extend from the abstract class AbstractEntity. The UserEntity is an example how this is done.

@AllArgsConstructor
@Data
@Entity
@EqualsAndHashCode(callSuper = false)
@NoArgsConstructor
@SuperBuilder
@Table(name = "USERS")
@ToString
@Validated
public class UserEntity extends AbstractEntity<UUID> {

  @NotBlank
  @Size(min = 3, max = 50)
  @Column(name = "FIRSTNAME", length = 50)
  private String firstname;

  @NotBlank
  @Size(min = 3, max = 50)
  @Column(name = "LASTNAME", length = 50)
  private String lastname;

  @NotBlank
  @Size(min = 3, max = 50)
  @Column(name = "PASSWORD", length = 50)
  @ToString.Exclude
  private String password;

  @OneToOne(cascade = CascadeType.ALL)
  @ToString.Exclude
  @JoinColumn(name = "DOCUMENT", referencedColumnName = "id")
  private DocumentEntity document;
}

The other classes can be found in the git-repo which I’ll link in the next few weeks.

The Curator-Project – Architecture Backend

Before I’ll start with the code some thoughts what has to bee done and what is additional work.

Data structure

How should the content be structured to display it to the user? The easiest way is to implement something that feels like a folder structure for navigation and add tags to get some clusters.

Entities

  • The category entity should work like a folder-structure. It references one parent category entity and might have some sub/child-categories.
  • The media entity links to category to get something similar like a path. Media groups contain multiple documents. In this way for example an audio book with more then one file can be accomplished.
  • The document entity references a single file. The path might contain a file path as well as a reference to a S3.
  • The tag entity clusters the media independent on category or media-type. For example it could group all media with excavator (song, instruction, images, …)
  • The user entity represents a basic user. For now this will be real basic and will be moved in future to the authentication service.

REST

The Frontend should communicate via REST with the Backend

Converter

To get an abstraction between the DB-Entities and the Rest-Endpoint we’ll implement some converters.

DTOs & Views

DTOs will act as a abstraction of an Entity. Views are an specialized object which might contain only a reduced set of an entity or a mix of several entities.

Database

As productive and development database I’ll use PostgreSQL. The schema is created by JPA

spring:
  jpa:
    database-platform: org.hibernate.dialect.PostgreSQLDialect
    show-sql: true
    generate-ddl: true
    hibernate:
      ddl-auto: create-drop
    properties:
      hibernate.jdbc.time_zone: UTC
      hibernate.format_sql: true

The attribute ddl-auto: create-drop creates the schema and the tables for the entities. Liquibase or something similar would be a much better approach, but would take much more effort for now.

Bootstrap

To get some initial data I’ll do this direct via Java. Like the setup of the DB schema technical it would be a much better approach to do this with Liquibase. The benefit of using the CRUD-Services is that these services can be tested on the bootup.

@Component
@DependsOn({"userBootstrap"})

public class CategoryBootstrap { 
 
    @PostConstruct
    public void init() {
      ...
    }
}

Document storage

As storage I’ll use Minio which is fully compatible with S3. I’m using tobi312/minio because it works on my raspberry pi. Feel free to use a different container like minio/minio.

This part of my docker-compose-file shows the configuration. of the minio-service

  minio:
    container_name: minio
    image: tobi312/minio:latest
    restart: always
    command: [ "server", "--address", ":9000", "--console-address", ":9001", "/data" ]
    environment:
      MINIO_ROOT_USER: admin
      MINIO_ROOT_PASSWORD: "TODO"
    ports:
      - "9000:9000"
      - "9001:9001"
    volumes:
      - "/opt/minio/data:/data:rw"
    healthcheck:
      test: [ "CMD", "curl", "--fail", "http://localhost:9000/minio/health/live" ]
      interval: 60s
      timeout: 10s
      retries: 3
    networks:
      - curatornet

The Curator-Project – Start

In order to get some experience on how the systems work and interact I’ll setup an example project. The code will be published as soon as have a first version up, running und basically tested.

Goal

Last year I’ve tried Amazon Kids and wasn’t relay impressed with the UI and the content.

I would like to use different services (Amazon Music, Spotify, …) and mix in my own content as audio books, music, videos, building instructions and so on in an UI that’s navigable for kids (4-7 years).

Requirements

The initial requirements needed to start with the implementation. This section will be updated when there are more.

Must have

  • Easy to navigate
    • Big icons
    • Works on Tablet
  • Own content can be shown
    • Instructions (pdf)
    • Audio-books
    • Music
    • Video
  • Own content can be added
  • Handle multiple user

Nice to have

  • Streaming of own data
  • Third party content
    • Amazon Music-Playlists
    • Youtube selected Videos
  • Multi-Language support

Development-Plan

To goal is to get a first look an the system as soon as possible. This might require bigger a refactoring later on, but as the UI is the key I’ll shortcut the evaluation of the requirements.

Architecture

As seen on the Platform overview there might bee a lot of services to get up an running before anything is visible. For now Ill shorthand this a little in order to get some experience on the essential services needed.

For Frontend I’ll use Angular. There are a lot of different alternatives that might be better, but as I am familiar with Angular I’ll stick with this framework. The same reason applies for Java/Spring.

So what is relay needed?

  • Frontend to interact with the content
  • Backens-Service for CRUD-Operations for the content
  • DB for storage data (PostgreSQL, MariaDB, …)
  • Service to store content (File system, S3, …)

The other service can be added later on. As it is not productive authorization for example can be mocked for the moment.