To give some context to the problem I'm trying to solve, I have two separate projects...
The first contains a set of APIs which are mostly defined via interfaces and contains very little implementation. This should be considered something that is consumable as a dependency; for example:
project-api
package com.newco.api.users;
public interface UserService {
void createUser(User user);
User getUserById(UUID id);
}
package com.newco.api.accounts;
public interface AccountService {
void createUserAccount(User user, Account account);
Account getAccountByUser(User user);
}
Users may consume these interfaces as part of the API. Note the relationship between users and accounts.
The second project contains the implementation of the APIs. These are not consumable and should never be seen by a developer; for example:
project-implementation
package com.newco.impl.users;
import com.newco.api.users.UserService;
final class UserServiceImpl implements UserService {
@Override
public void createUser(User user) { ... }
@Override
public User getUserById(UUID id) { ... }
}
package com.newco.impl.accounts;
import com.newco.api.users.UserService;
import com.newco.api.accounts.AccountService;
// This is problematic (see below)...
import com.newco.impl.users.UserSeviceImpl;
final class AccountServiceImpl implements AccountService {
// This is problematic (see below)...
private final UserService userService = new UserServiceImpl();
@Override
public void createUserAccount(User user, Account account) { ... }
@Override
public Account getAccountByUser(User user) { ... }
}
Putting aside the obvious violation of the Dependency Inversion Principle, as it really isn't important for the sake of this question, the problem is that I can't access UserServiceImpl
from AccountServiceImpl
because it's package-private - I can't even import it.
In contrast, if I were to implement this with Kotlin, the problem goes away entirely; for example:
project-implementation-kotlin
package com.newco.impl.users
import com.newco.api.users.UserService
internal class UserServiceImpl : UserService {
fun createUser(user: User) { ... }
fun getUserById(id: UUID): User { ... }
}
package com.newco.impl.accounts
import com.newco.api.users.UserService
import com.newco.api.accounts.AccountService
import com.newco.impl.users.UserSeviceImpl
internal class AccountServiceImpl implements AccountService {
private val userService = UserServiceImpl()
fun createUserAccount(user: User, account: Account) { ... }
fun getAccountByUser(user: User): Account { ... }
}
My expectation was that Java's package-private accessibility works the same way as Kotlin's internal
modifier, but it seems they are subtly different.
Currently, it seems that if I want to implement these interfaces with Java, I have two choices:
I make the classes public, and therefore accessible, but am able to organise the classes into separate packages.
I can make the classes package-private, and therefore inaccessible, but am not able to organise them into separate packages.
Neither of these choices are ideal. I would rather keep the implementations inaccessible, and organised.
How does internal
differ from package-private, and is there a way to achieve the equivalent of internal
in Java? Do Java 9 modules solve this problem in any way?
Note: I'm targeting Java 11