Skip to content

Conversation

carter-cundiff
Copy link
Contributor

@carter-cundiff carter-cundiff commented Dec 13, 2024

Some metamodel classes (ex: RecordFieldTypeElement.java) need access to the current ModelInstanceRepository before the GenerationContext has been created.

Given MIR_A implements ModelInstanceRepository and MIR_B extends MIR_A, the old way fails when the implementation class for an execution is MIR_B but the upstream fermenter project still contains references to MIR_A. The new way allows for those references to remain intact while allowing others to extend their project.

Old way would return a NPE as MIR_A.class doesn't exist in the map, only MIR_B.class:

MIR_A modelRepository = ModelInstanceRepositoryManager.getMetamodelRepository(MIR_A.class);

* class to lookup
* @return instance of that class
*/
public static <V> V getMetamodelRepositoryWithExtensions(Class<V> type) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

S: This approach feels like it leaves a lot of room for ambiguity about what you're going to get. If you have multiple repositories that extend some common class, and then call this method with that common class, you arbitrarily get what comes first.

I propose we overload setRepository with a version that takes the repository and the class to register it as. So you'd call something like setRepository(BaseRepo.class, new MyExtensionRepo()). Then on lookup, it's completely unambiguous. BaseRepo should always return MyExtensionRepo and only MyExtensionRepo. Of course it's still possible that the method be called multiple times with the same key, but that's just the nature of lookup tables/maps I think.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also has the benefit of not forcing all implementers to change their calls of getMetamodelInstanceRepository with getMetamodelInstanceRepositoryWithExtension.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setRepository() is called as part of fermenter-mda/src/main/java/org/technologybrewery/fermenter/mda/GenerateSourcesHelper.java so it has no way of knowing the base class and extension class as it only has access to metadataRepositoryImpl.

Copy link
Contributor Author

@carter-cundiff carter-cundiff Dec 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could interrogate the superclass from the current instance but I think that might break as more layers are added. Need to look into it more.

Copy link
Contributor Author

@carter-cundiff carter-cundiff Dec 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given MIR_A implements AbstractModelInstanceRepository, MIR_B extends MIR_A, and MIR_C extends MIR_B.

When we're executing with the MIR_C repository as our current implementation, we would write:

setRepository(MIR_B.class, MIR_C)

This would still cause breakage in the MIR_A repository on the calls:

getMetamodelRepository(MIR_A.class)

Unless we want to continue interrogating the given impl class until we hit AbstractModelInstanceRepository. Then we would need multiple writes to the set repository for each layer:

setRepository(MIR_C.class, MIR_C)
setRepository(MIR_B.class, MIR_C)
setRepository(MIR_A.class, MIR_C)

This would allow all calls to get the same MIR_C instance:

getMetamodelRepository(MIR_A.class) -> MIR_C
getMetamodelRepository(MIR_B.class) -> MIR_C
getMetamodelRepository(MIR_C.class) -> MIR_C

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think from a usage perspective you're telling Fermenter what impl class should be used for a given repository type. I don't think it's a huge deal that you'd have to set it explicitly at multiple levels, because I don't think that would be the correct solution in 99% of cases.

If I register MIR_B as the impl for MIR_A, I should still write my consuming code at the MIR_A level, even in the source that's "aware" of the B impl. And so if B were then extended with C, again it's just replacing the impl of A and all code should be written against A.

We could enforce something around it being an interface if that would feel more natural. That would call out that there is a single interface being programmed against and the impl is decided/provided at runtime. My only concern with requiring an interface would be old code. We'd either need old code to be migrated to be interface-driven, or we'd need to allow both interface-driven and concrete class which seems like it could lead to a lot of edge cases/ambiguity.

Copy link
Contributor Author

@carter-cundiff carter-cundiff Dec 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if I follow correctly, you're saying that even in the MIR_B and MIR_C repos, they should still really only be using:

getMetamodelRepository(MIR_A.class)

Given we want to preserve the existing setRepository() rather than make it an interface and introduce a breaking change, we would still need to interrogate the impl until we find the base class. If we're doing that interrogation I don't see any harm in adding all the intermediate classes along the way:

    public static void setRepository(ModelInstanceRepository repository) {
        Map<String, Object> instanceMap = threadBoundInstance.get();

        instanceMap.put(repository.getClass().toString(), repository);
        Class<?> repositorySuperClass = repository.getClass().getSuperclass();

        while (!repositorySuperClass.getName().equals("org.technologybrewery.fermenter.mda.metamodel.AbstractModelInstanceRepository")) {
            instanceMap.put(repositorySuperClass.toString(), repository);
            repositorySuperClass = repositorySuperClass.getSuperclass();
        }
    }

@carter-cundiff carter-cundiff force-pushed the 78-enable-metamodel-extension branch 2 times, most recently from 6b38b0e to 1e1ca5c Compare December 13, 2024 18:19

/**
* Adds a repository. Only one repository of each type will be kept.
* Adds a repository and creates references to it for the current impl class and any super
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A:

Suggested change
* Adds a repository and creates references to it for the current impl class and any super
* Sets a repository as the repository implementation for its own class and any super

…tory implementation outside of the GenerationContext
@carter-cundiff carter-cundiff force-pushed the 78-enable-metamodel-extension branch from 1e1ca5c to 504014b Compare December 13, 2024 18:24
@carter-cundiff carter-cundiff merged commit 8849d14 into dev Dec 13, 2024
3 checks passed
@carter-cundiff carter-cundiff deleted the 78-enable-metamodel-extension branch December 13, 2024 18:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Retrieval mechanisms for the active ModelInstanceRepository inhibit extension patterns.

2 participants