Çözüldü Java Token modeli ile polimorfizim yapmak mantıklı olur mu?

Bu konu çözüldü olarak işaretlenmiştir. Çözülmediğini düşünüyorsanız konuyu rapor edebilirsiniz.

706111

Hectopat
Katılım
28 Ağustos 2023
Mesajlar
6.020
Makaleler
1
Çözümler
29
Arkadaşlar merhaba.

E-posta doğrulama sistemi yaptım. Token oluşturup, e-posta mesajı atıyor ve bu mesajdaki linke tıkladığınız zaman URL'deki API, yine URL içindeki string token parametresine ile erişip, hesabı aktif ediyor.

Şimdi birkaç tane daha token oluşturmam gerek. Mesela şifre sıfırlama için.

Sizce tek bir token oluşturup, diğerlerini bundan miras almasını sağlamak ve yine bu tek token için önceden oluşturduğumuz tek katmanlı mimari (exception, repository, service, event) yapısını diğer token modelleri de içinde kullanmak (tabii mümkünse böyle bir şey) doğru bir yaklaşım mı olur?

Yoksa sadece enum ile bir ayıraç mı kullanayım?
 
Çözüm
Bu tür repo, servis kullanımı için şöyle yapılması lazım (direkt ezberden kodu, annotationları vb. tam haliyle hatırlayamadığım için chatgptden biraz yardım aldım itiraf edeyim :) Direkt kodu denemediğim için bazı yerleri yanlış olabilir ama örneklerin kastettiğimiz mantığı vereceğini düşünüyorum. Discriminator diye bir şey kullanılır bu durumda;

Kod:
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "token_type", discriminatorType = DiscriminatorType.STRING)
public abstract class Token {

böyle bir abstract entity sınıfında, token nesnesinin fieldları, yani id, token, userid, expiredate vb. ve tokenın tipini belirten bir kolon (bu otomatik eklenir, diğerlerini @Column private String token gibi sizin eklemeniz lazım) olacak.

Kod:
@Entity
@DiscriminatorValue("RESET_PASS")
public class ResetPassToken extends Token {

//ya da

@Entity
@DiscriminatorValue("ACTIVATE_ACC")
public class ActivateAccToken extends Token {

Token sınıfını extend eden böyle sınıflarda da discriminatorValue ile hangi token tipi işlem görüyor bu ayırt edilir (token_type).

Kod:
@Service
public class TokenService {

    private final TokenRepository<Token> tokenRepository;

    public TokenService(TokenRepository<Token> tokenRepository) {
        this.tokenRepository = tokenRepository;
    }

    public void resetPass(String token, String newPassword) {
        Token resetPasswordToken = tokenRepository.findByToken(token)
            .orElseThrow(() -> new IllegalArgumentException("Invalid token"));

        if (!(resetPasswordToken instanceof ResetPassToken)) {
            throw new IllegalArgumentException("Invalid token type");
        }

//activasyon işlemi ve diğer başka işler olacaksa onlar için de servis methodları eklenmeli...

Şöyle tek bir servis üzerinden işler yürütülebilir, tokenRepository ile dbden token çekilir ve bu hangi tip entity (reset mi, aktivasyon mu, hesap iptali mi vs.) kontrolü yapılır (token tipine göre classın instance tipi, Token classının hangi türü diye anlaşılıyor farkındaysanız).

Böyle daha basit gözükse de aslında servisleri her bir business logic için çoğaltmak gerektiğini düşünüyorum, daha başka şeyler de eklenecekti, yeni method lazım vb. derken kodlar daha bir çorbaya dönüyor genellikle. Şu şekilde daha karmaşık gözükse de sorumlulukları ayırdığımızda aslında daha temiz olacaktır;

Kod:
public interface BaseTokenService<T extends Token> {
//mesela findByToken methodu, her token tipinin servislerinde kullanılacak bir method

Kod:
public abstract class AbstractTokenService<T extends Token> implements BaseTokenService<T> {

    protected final TokenRepository<T> tokenRepository;

    protected AbstractTokenService(TokenRepository<T> tokenRepository) {
        this.tokenRepository = tokenRepository;
    }

    @Override
    public Optional<T> findByToken(String token) {
        return tokenRepository.findByToken(token);
    }
   
    //interfaceimizi implemente eden bu abstract classta methodun içi dolduruluyor

Bundan sonra da her bir business logic için ayrı servisler tanımlanabilir, mesela

Kod:
@Service
public class ResetPasswordService extends AbstractTokenService<ResetPassToken> {

    public ResetPasswordService(TokenRepository<ResetPassToken> tokenRepository) {
        super(tokenRepository);
    }

    public void resetPassword(String token, String newPassword) {
        ResetPassToken resetPasswordToken = findByToken(token)
            .orElseThrow(() -> new IllegalArgumentException("Invalid token"));
           
            //bundan sonra da reset password işleminde ne yapılacak ise onların kodu eklenir

Kod:
@Service
public class ActivateAccountService extends AbstractTokenService<ActivateAccToken> {

    public ActivateAccountService(TokenRepository<ActivateAccToken> tokenRepository) {
        super(tokenRepository);
    }

    public void activateAccount(String token) {
        ActivateAccToken activateAccountToken = findByToken(token)
            .orElseThrow(() -> new IllegalArgumentException("Invalid token"));
           
            //benzer şekilde aktivasyon logic burada yazılır

Controller üzerinden de birden fazla servise şöyle erişirsiniz,

Kod:
//controller sınıfında

    private final ResetPasswordService resetPasswordService;
    private final ActivateAccountService activateAccountService;

    public TokenController(ResetPasswordService resetPasswordService,
                           ActivateAccountService activateAccountService) {
        this.resetPasswordService = resetPasswordService;
        this.activateAccountService = activateAccountService;
    }
   
    //reset pass ucuna istek geldiğinde
   
     resetPasswordService.resetPassword(token, newPassword);
   
   
     //activasyon ucuna istek geldiğinde
   
     activateAccountService.activateAccount(token);

Başka token işlemleri için yeni servisler tanımlanır, kod derli toplu olur, tek servisteki gibi alt alta çok sayıda method yığılmaz, ayrı ayrı servislerin unit testini yazmak da kolaylaşır. Direkt doğrusu şudur budur demiyorum, gereksinimler analizi iyi yapılırsa, daha sonra şuna buna ihtiyaç olması ihtimali varsa, kodlara sürekli ekleme yapmak gerekecekse vb. mimariyi daha sağlam tutmakta fayda var, eğer servisin büyümesi, farklı farklı logicler eklenmesi durumu yoksa en basit haliyle tek bir servis üzerinden de işler yürütülebilir, ekleme vs. yapılmayacaksa en basit şekilde iş gören ve en kısa sürede yazılan, başkası tarafından hızlıca anlaşılabilecek hali en iyisidir.
Sizce tek bir token oluşturup, diğerlerini bundan miras almasını sağlamak ve yine bu tek token için önceden oluşturduğumuz tek katmanlı mimari (exception, repository, service, event) yapısını diğer token modelleri de içinde kullanmak (tabii mümkünse böyle bir şey) doğru bir yaklaşım mı olur?

Yoksa sadece enum ile bir ayıraç mı kullanayım?
Her token modeli için ayrı exception, service, repository oluşturma gibi bir planınız yok değil mi, yoksa ben yanlış anladım herhalde.

Bu arada servis, repo vb. modüler bir yapınız varsa neye göre tek katmanlı mimari diyorsunuz buna.

Basitçe benim aklıma şöyle basit bir yapı geliyor, jwt tokenı alındığında (sizin token nasıl oluşuyor bilmiyorum, random string de yaratıyor olabilirsiniz tabi), bunun içinde bir tip değeri olur, mesela "activation". Siz de web servisinizde bu tip değerini okursunuz.
Bir tane TokenValidator interfaceiniz olur içinde mesela validate(token) gibi bir method ile; bunu implemente eden birden fazla validator sınıfı yaratırsınız, ActivationValidator, PassResetValidator vb.
Eğer tip = "activation" ise (tipleri tutmak için bir enum eklenebilir bence burada, MailType.ACTIVATION eşitse gelen tip değerine) ActivationValidator sınıfındaki validate(token) methodunu çağırırsınız, şifre sıfırlama tipinde ise PassResetValidatordaki validate methodu çağrılır vs.

Ben böyle bir sorununuz var diye anladım ve böyle bir öneri sunuyorum, eğer yanlış anladı isem siz basitçe yapınızı anlatın sonra da doğru bir yaklaşım mı olur diye kafanızda kurduğunuz yapıdan bahsedin, tokendan miras almak, tek katmanlı mimari, ayıraç vb. biraz kafamı karıştırmış olabilir.
 
Her token modeli için ayrı exception, service, repository oluşturma gibi bir planınız yok değil mi ...
Hocam işte bundan kaçınmak için Token ve Token sınıfından türemiş nesneleri, tek bir TokenRepository, TokenService vs. üzerinde kullanmak yani.

Yani buna benzer;
Java:
@Repository public interface TokenRepository<T extends Token> extends JpaRepository<T, Long> {
    Optional<T> findByToken(String token);
    List<T> findByUser(User user);
}
Java:
@Service
public interface TokenService<T extends Token> {
    List<T> userTokens(User user);
    T getToken(String token);
    T createToken(User user, String token);
    Boolean verificationToken(T token);
}

Ya da enum ile ayırmak ama proje esnekliğini öldürebilir belki. Çok geniş bir proje değil ama doğru bir yaklaşımla yapmak istiyorum.

Bu arada servis, repo vb. modüler bir yapınız varsa neye göre tek katmanlı mimari diyorsunuz buna.
Yanlış biliyorum burayı. Tek model üzerinden bağımlılıklardan dolayı tek katmanlı mimari diye kalmış aklımda. Normalde katmanlı mimari ya da başka bir şey olabilir.
 
Bu tür repo, servis kullanımı için şöyle yapılması lazım (direkt ezberden kodu, annotationları vb. tam haliyle hatırlayamadığım için chatgptden biraz yardım aldım itiraf edeyim :) Direkt kodu denemediğim için bazı yerleri yanlış olabilir ama örneklerin kastettiğimiz mantığı vereceğini düşünüyorum. Discriminator diye bir şey kullanılır bu durumda;

Kod:
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "token_type", discriminatorType = DiscriminatorType.STRING)
public abstract class Token {

böyle bir abstract entity sınıfında, token nesnesinin fieldları, yani id, token, userid, expiredate vb. ve tokenın tipini belirten bir kolon (bu otomatik eklenir, diğerlerini @Column private String token gibi sizin eklemeniz lazım) olacak.

Kod:
@Entity
@DiscriminatorValue("RESET_PASS")
public class ResetPassToken extends Token {

//ya da

@Entity
@DiscriminatorValue("ACTIVATE_ACC")
public class ActivateAccToken extends Token {

Token sınıfını extend eden böyle sınıflarda da discriminatorValue ile hangi token tipi işlem görüyor bu ayırt edilir (token_type).

Kod:
@Service
public class TokenService {

    private final TokenRepository<Token> tokenRepository;

    public TokenService(TokenRepository<Token> tokenRepository) {
        this.tokenRepository = tokenRepository;
    }

    public void resetPass(String token, String newPassword) {
        Token resetPasswordToken = tokenRepository.findByToken(token)
            .orElseThrow(() -> new IllegalArgumentException("Invalid token"));

        if (!(resetPasswordToken instanceof ResetPassToken)) {
            throw new IllegalArgumentException("Invalid token type");
        }

//activasyon işlemi ve diğer başka işler olacaksa onlar için de servis methodları eklenmeli...

Şöyle tek bir servis üzerinden işler yürütülebilir, tokenRepository ile dbden token çekilir ve bu hangi tip entity (reset mi, aktivasyon mu, hesap iptali mi vs.) kontrolü yapılır (token tipine göre classın instance tipi, Token classının hangi türü diye anlaşılıyor farkındaysanız).

Böyle daha basit gözükse de aslında servisleri her bir business logic için çoğaltmak gerektiğini düşünüyorum, daha başka şeyler de eklenecekti, yeni method lazım vb. derken kodlar daha bir çorbaya dönüyor genellikle. Şu şekilde daha karmaşık gözükse de sorumlulukları ayırdığımızda aslında daha temiz olacaktır;

Kod:
public interface BaseTokenService<T extends Token> {
//mesela findByToken methodu, her token tipinin servislerinde kullanılacak bir method

Kod:
public abstract class AbstractTokenService<T extends Token> implements BaseTokenService<T> {

    protected final TokenRepository<T> tokenRepository;

    protected AbstractTokenService(TokenRepository<T> tokenRepository) {
        this.tokenRepository = tokenRepository;
    }

    @Override
    public Optional<T> findByToken(String token) {
        return tokenRepository.findByToken(token);
    }
   
    //interfaceimizi implemente eden bu abstract classta methodun içi dolduruluyor

Bundan sonra da her bir business logic için ayrı servisler tanımlanabilir, mesela

Kod:
@Service
public class ResetPasswordService extends AbstractTokenService<ResetPassToken> {

    public ResetPasswordService(TokenRepository<ResetPassToken> tokenRepository) {
        super(tokenRepository);
    }

    public void resetPassword(String token, String newPassword) {
        ResetPassToken resetPasswordToken = findByToken(token)
            .orElseThrow(() -> new IllegalArgumentException("Invalid token"));
           
            //bundan sonra da reset password işleminde ne yapılacak ise onların kodu eklenir

Kod:
@Service
public class ActivateAccountService extends AbstractTokenService<ActivateAccToken> {

    public ActivateAccountService(TokenRepository<ActivateAccToken> tokenRepository) {
        super(tokenRepository);
    }

    public void activateAccount(String token) {
        ActivateAccToken activateAccountToken = findByToken(token)
            .orElseThrow(() -> new IllegalArgumentException("Invalid token"));
           
            //benzer şekilde aktivasyon logic burada yazılır

Controller üzerinden de birden fazla servise şöyle erişirsiniz,

Kod:
//controller sınıfında

    private final ResetPasswordService resetPasswordService;
    private final ActivateAccountService activateAccountService;

    public TokenController(ResetPasswordService resetPasswordService,
                           ActivateAccountService activateAccountService) {
        this.resetPasswordService = resetPasswordService;
        this.activateAccountService = activateAccountService;
    }
   
    //reset pass ucuna istek geldiğinde
   
     resetPasswordService.resetPassword(token, newPassword);
   
   
     //activasyon ucuna istek geldiğinde
   
     activateAccountService.activateAccount(token);

Başka token işlemleri için yeni servisler tanımlanır, kod derli toplu olur, tek servisteki gibi alt alta çok sayıda method yığılmaz, ayrı ayrı servislerin unit testini yazmak da kolaylaşır. Direkt doğrusu şudur budur demiyorum, gereksinimler analizi iyi yapılırsa, daha sonra şuna buna ihtiyaç olması ihtimali varsa, kodlara sürekli ekleme yapmak gerekecekse vb. mimariyi daha sağlam tutmakta fayda var, eğer servisin büyümesi, farklı farklı logicler eklenmesi durumu yoksa en basit haliyle tek bir servis üzerinden de işler yürütülebilir, ekleme vs. yapılmayacaksa en basit şekilde iş gören ve en kısa sürede yazılan, başkası tarafından hızlıca anlaşılabilecek hali en iyisidir.
 
Çözüm
Bu tür repo, servis kullanımı için şöyle yapılması lazım (direkt ezberden kodu, annotationları vb. tam haliyle hatırlayamadığım için chatgptden biraz yardım aldım itiraf edeyim :) Direkt kodu denemediğim için bazı yerleri yanlış olabilir ama örneklerin kastettiğimiz mantığı vereceğini düşünüyorum. Discriminator diye bir şey kullanılır bu durumda;

Kod:
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "token_type", discriminatorType = DiscriminatorType.STRING)
public abstract class Token {

böyle bir abstract entity sınıfında, token nesnesinin fieldları, yani id, token, userid, expiredate vb. ve tokenın tipini belirten bir kolon (bu otomatik eklenir, diğerlerini @Column private String token gibi sizin eklemeniz lazım) olacak.

Kod:
@Entity
@DiscriminatorValue("RESET_PASS")
public class ResetPassToken extends Token {

//ya da

@Entity
@DiscriminatorValue("ACTIVATE_ACC")
public class ActivateAccToken extends Token {

Token sınıfını extend eden böyle sınıflarda da discriminatorValue ile hangi token tipi işlem görüyor bu ayırt edilir (token_type).

Kod:
@Service
public class TokenService {

    private final TokenRepository<Token> tokenRepository;

    public TokenService(TokenRepository<Token> tokenRepository) {
        this.tokenRepository = tokenRepository;
    }

    public void resetPass(String token, String newPassword) {
        Token resetPasswordToken = tokenRepository.findByToken(token)
            .orElseThrow(() -> new IllegalArgumentException("Invalid token"));

        if (!(resetPasswordToken instanceof ResetPassToken)) {
            throw new IllegalArgumentException("Invalid token type");
        }

//activasyon işlemi ve diğer başka işler olacaksa onlar için de servis methodları eklenmeli...

Şöyle tek bir servis üzerinden işler yürütülebilir, tokenRepository ile dbden token çekilir ve bu hangi tip entity (reset mi, aktivasyon mu, hesap iptali mi vs.) kontrolü yapılır (token tipine göre classın instance tipi, Token classının hangi türü diye anlaşılıyor farkındaysanız).

Böyle daha basit gözükse de aslında servisleri her bir business logic için çoğaltmak gerektiğini düşünüyorum, daha başka şeyler de eklenecekti, yeni method lazım vb. derken kodlar daha bir çorbaya dönüyor genellikle. Şu şekilde daha karmaşık gözükse de sorumlulukları ayırdığımızda aslında daha temiz olacaktır;

Kod:
public interface BaseTokenService<T extends Token> {
//mesela findByToken methodu, her token tipinin servislerinde kullanılacak bir method

Kod:
public abstract class AbstractTokenService<T extends Token> implements BaseTokenService<T> {

    protected final TokenRepository<T> tokenRepository;

    protected AbstractTokenService(TokenRepository<T> tokenRepository) {
        this.tokenRepository = tokenRepository;
    }

    @Override
    public Optional<T> findByToken(String token) {
        return tokenRepository.findByToken(token);
    }
 
    //interfaceimizi implemente eden bu abstract classta methodun içi dolduruluyor

Bundan sonra da her bir business logic için ayrı servisler tanımlanabilir, mesela

Kod:
@Service
public class ResetPasswordService extends AbstractTokenService<ResetPassToken> {

    public ResetPasswordService(TokenRepository<ResetPassToken> tokenRepository) {
        super(tokenRepository);
    }

    public void resetPassword(String token, String newPassword) {
        ResetPassToken resetPasswordToken = findByToken(token)
            .orElseThrow(() -> new IllegalArgumentException("Invalid token"));
        
            //bundan sonra da reset password işleminde ne yapılacak ise onların kodu eklenir

Kod:
@Service
public class ActivateAccountService extends AbstractTokenService<ActivateAccToken> {

    public ActivateAccountService(TokenRepository<ActivateAccToken> tokenRepository) {
        super(tokenRepository);
    }

    public void activateAccount(String token) {
        ActivateAccToken activateAccountToken = findByToken(token)
            .orElseThrow(() -> new IllegalArgumentException("Invalid token"));
        
            //benzer şekilde aktivasyon logic burada yazılır

Controller üzerinden de birden fazla servise şöyle erişirsiniz,

Kod:
//controller sınıfında

    private final ResetPasswordService resetPasswordService;
    private final ActivateAccountService activateAccountService;

    public TokenController(ResetPasswordService resetPasswordService,
                           ActivateAccountService activateAccountService) {
        this.resetPasswordService = resetPasswordService;
        this.activateAccountService = activateAccountService;
    }
 
    //reset pass ucuna istek geldiğinde
 
     resetPasswordService.resetPassword(token, newPassword);
 
 
     //activasyon ucuna istek geldiğinde
 
     activateAccountService.activateAccount(token);

Başka token işlemleri için yeni servisler tanımlanır, kod derli toplu olur, tek servisteki gibi alt alta çok sayıda method yığılmaz, ayrı ayrı servislerin unit testini yazmak da kolaylaşır. Direkt doğrusu şudur budur demiyorum, gereksinimler analizi iyi yapılırsa, daha sonra şuna buna ihtiyaç olması ihtimali varsa, kodlara sürekli ekleme yapmak gerekecekse vb. mimariyi daha sağlam tutmakta fayda var, eğer servisin büyümesi, farklı farklı logicler eklenmesi durumu yoksa en basit haliyle tek bir servis üzerinden de işler yürütülebilir, ekleme vs. yapılmayacaksa en basit şekilde iş gören ve en kısa sürede yazılan, başkası tarafından hızlıca anlaşılabilecek hali en iyisidir.
Hocam dediğiniz gibi yaptım birçok kısmı. Token sınıfı abstract yaptım. @Inherintace(PER_CLASS) seçeneği varmış. Ayrı ayrı tablo oluşturdu ve abstract sınıfın da tablosunu da oluşturmadım.

@Inherintace annotasyonu ile generic type yapmama da gerek kalmadı.

Ayrıca tek service üzerinden doğrulama, sıfırlama token oluşturmasını yaptım.

Sizin kodları daha inceliyorum ben. Birkaç güzel nokta var, çalışacağım.
 
Token transient olmali, DB de gerekmedikce saklanmamali cunku zaman asimina ugramasi gerekiyor.

Kullaniciya token ureten ve bunu belirledigin zamanla sinirlandiran JWT ya da Paseto servisi yazabilirsin.

Java:
interface TokenService{
String generateToken(String key, Duration ttl);
String resolveToken(String token) throws TokenException;
}

Bu interface i implemente eden herhangi bir servis, sistemin herhangi bir katmaninda kullanilabilir. Password recovery, email validation ya da herhangi zamanlanmis kisa sureli token uretiminde kullanilabilir. Sadece "key" degerini DB de saklayabilirsin onu da isin bitip audit ettikten sonra silersin.

Sistem buyudukce token'lar bos yere DB de kalacak, hepsi zaman asimina ugradigi icin bir anlami da olmayacak. Genel olarak hizlica degisen seyleri buyuk oranda degismeyecek seylerle birlikte saklamaktan kaciniriz mimari olarak. O yuzden email'i sakladigin tabloda token saklamak uygun degil.
 
Token transient olmali, DB de gerekmedikce saklanmamali cunku zaman asimina ugramasi gerekiyor.

Kullaniciya token ureten ve bunu belirledigin zamanla sinirlandiran JWT ya da Paseto servisi yazabilirsin.

Java:
interface TokenService{
String generateToken(String key, Duration ttl);
String resolveToken(String token) throws TokenException;
}

Bu interface i implemente eden herhangi bir servis, sistemin herhangi bir katmaninda kullanilabilir. Password recovery, email validation ya da herhangi zamanlanmis kisa sureli token uretiminde kullanilabilir. Sadece "key" degerini DB de saklayabilirsin onu da isin bitip audit ettikten sonra silersin.

Sistem buyudukce token'lar bos yere DB de kalacak, hepsi zaman asimina ugradigi icin bir anlami da olmayacak. Genel olarak hizlica degisen seyleri buyuk oranda degismeyecek seylerle birlikte saklamaktan kaciniriz mimari olarak. O yuzden email'i sakladigin tabloda token saklamak uygun degil.
Hocam e-mail'i sakladığım tabloda saklamadım. Ayrı tablo yapmıştım. Token ile User arasında ilişki kurdum ManyToOne diye.
Veritabanında şimdilik 3 tablo var. User, Verifcation Token ve ResetPasswordToken diye.
 

Technopat Haberler

Geri
Yukarı