Two versions of app: open source and enterprise

Two versions of app: open source and enterprise
typescript
Ethan Jackson

I've got an open source app written in Rust. I'm in the process of adding an enterprise version of it. 99% of the functionality is in the open source version, and the enterprise version just needs to replace a few of the underlying functions. I'd like to keep the enterprise code closed-source.

What's the best project layout?

It would have to be two separate git repos (I think) if I want one to be open and the other closed.

I could have the enterprise version depend on the open source version, but I'm having a hard time replacing the underlying functions in the open source version with enterprise functions. Rust won't let me swap out code that exists in a dependency, even with a feature flag, because we'd have a circular dependency: enterprise depends on the open source, but the open source must access enterprise code.

I've tried tricks with the #[path] directive to have the open source pull a file from the enterprise project, but that has led to dependency hell.

Surely this is a solved problem. How do other people do it?

Answer

Traits can do this.

Say you have a function in your open source version that does something:

pub fn run() { println!("this is open source"); }

You can make a trait that has items for all the things you want to change. Then make your run function generic.

pub trait Version { fn f(); } pub fn run<V: Version>() { V::f(); }

Then you can implement the trait with your open source behavior and use it in your app.

pub struct OpenSource; impl Version for OpenSource { fn f() { println!("this is open source"); } } fn main() { run::<OpenSource>(); }

Then the enterprise version can include the open source library as a dependency, implement the trait, and use that implementation in its app.

pub struct Enterprise; impl your_app::Version for Enterprise { fn f() { println!("this is enterprise"); } } fn main() { your_app::run::<Enterprise>(); }

You will most likely have many functions and possibly some associated types in your trait, and need to pass the generic through many functions.

Related Articles