Fork me on GitHub
#polylith
<
2023-01-14
>
emccue03:01:19

If anyone is curious, I resumed the "polylith in java" experiment I was doing 5 months ago. Details in thread

polylith 2
emccue03:01:05

The strategy is still that a "brick" in polylith translates to a module in Java

emccue03:01:44

For Interface bricks:

emccue03:01:39

It is expected that four things are exposed 1. A "service" with all the methods that the component requires.

public interface ArticleService {
    Article findById(long id);
}
2. A "factory" which makes the service
public interface ArticleServiceFactory {
    ArticleService create(DataSource dataSource);
}
3. A mechanical interface for something which just "has" the service
public interface HasArticleService {
    ArticleService articleService();
}
4. Any classes which are needed to model the domain
public record Article(
        long articleId,
        ExternalId externalId,
        String title,
        String description,
        String body,
        LocalDateTime createdAt,
        LocalDateTime updatedAt,
        long userId
) {
    public ArticleSlug slug() {
        return new ArticleSlug(externalId, title);
    }
}
Then in the module, requires and exports as needed
module jolly.articles {
    exports dev.mccue.polylith.articles;
    
    requires transitive java.sql;
}
In addition to a "uses" declaration for the factory interface
module jolly.articles {
    exports dev.mccue.polylith.articles;

    requires transitive java.sql;

    uses dev.mccue.polylith.articles.ArticleServiceFactory;
}
And in the interface for the service, have a static method which delegates to a factory found via the service loader
public interface ArticleService {
    static ArticleService create(DataSource dataSource) {
        return ServiceLoader.load(ArticleServiceFactory.class)
                .findFirst()
                .orElseThrow()
                .create(dataSource);
    }
}

emccue03:01:07

For Component/implementation bricks:

emccue03:01:56

In the module nothing should be exposed directly, just a transitive require of the interface

module jolly.articles.impl {
    requires transitive jolly.articles;
}
And implementations should be provided for both the factory and service interfaces
public final class ArticleServiceImpl implements ArticleService {
    private final DataSource dataSource;
    public ArticleServiceImpl(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    
    // ...
}
public final class ArticleServiceFactoryImpl implements ArticleServiceFactory {
    @Override
    public ArticleService create(DataSource context) {
        return new ArticleServiceImpl(context);
    }
}
And the service factory should be declared in the module
module jolly.articles.impl {
    requires transitive jolly.articles;

    provides ArticleServiceFactory with ArticleServiceFactoryImpl;
}

emccue03:01:02

For "Base" bricks:

emccue03:01:12

Basically everything but the main, taking in some "ctx" object which holds all the services started or whatever else

public final class Handler<Ctx extends HasArticleService>
        implements Function<Request, IntoResponse> {
    private final Ctx ctx;

    public Handler(Ctx ctx) {
        this.ctx = ctx;
    }

    @Override
    public IntoResponse apply(Request request) {
        return new Response(Body.fromString(
                "Hello Polylith! " + ctx.articleService()
        ));
    }
}
And export the "entrypoint"
module jolly.handler {
    exports dev.mccue.polylith.handler;
    
    requires transitive jolly.articles;

    requires transitive dev.mccue.rosie;
}

emccue03:01:26

For project bricks: Actually select implementation bricks in the module

module jolly.main {
    requires jolly.articles.impl;
    requires jolly.db.impl;

    requires jolly.handler;
    requires dev.mccue.rosie;
    requires rosie.microhttp;
    requires org.microhttp;
    requires org.xerial.sqlitejdbc;
}
And start the server/whatever
public record Context(
        DataSource dataSource
) implements HasArticleService {
    public Context() {
        this(DB.create());
    }

    @Override
    public ArticleService articleService() {
        return ArticleService.create(dataSource);
    }
}
public class Main {
    public static void main(String[] args) {
        MicrohttpAdapter.runServer(
                new Handler<>(new Context()),
                new Options().withPort(1123),
                Executors.newFixedThreadPool(8)
        );
    }
}

emccue03:01:37

Doing this is, I think understandably, a bit exhausting

emccue03:01:58

like, not the technique, but working on something that isn't real for no benefit

emccue03:01:13

so I doubt i will ever finish the real world spec at this pace

emccue03:01:30

but I think I have a grasp of at least one way "it can be done"

emccue03:01:52

The Has interfaces are to avoid the need for DI

emccue03:01:12

and the dynamic linkage means some annoying stuff, but all in all I find it okay