Lab5 Luty Rodrigues

aluno: Luty Rodrigues Ribeiro
ano/sem: 2008/2o.
data do laboratório (num. da semana) : 17/12/2008 (12)

Introdução

Este laboratório tem como objetivo final desenvolver o servidor de um "Bolão Virtual", que consiste de um sistema de apostas (no nosso caso, em partidas de futebol). A idéia é que este servidor possa ser integrado ao lab6, que é o cliente do Bolão Virtual. Ao longo do desenvolvimento deste laboratório, vão sendo abordados vários tópicos vistos nas últimas semanas de aula.

Desenvolvimento

A primeira parte foi melhorar os decorators de Thread Safety e de Persistência da classe MatchList, que, conforme foi passado nas instruções, tinha problemas com os métodos make_bet e finalize da classe Match, que quebravam os requisitos de Thread Safety e de Persistência. Essa melhoria foi implementada criando as classes indicadas nas instruções, da maneira como foi descrito. Abaixo, apresentamos a implementação das 4 classes adicionais propostas:

require 'monitor'
require 'decorators/decorator'
 
module Decorators
  class MatchThreadSafeDecorator < Decorator
    include MonitorMixin
 
    def make_bet(score, user)
      synchronize do
        @decorated.make_bet(score, user)
      end
    end
 
    def finalize(final_score)
      synchronize do        
        @decorated.finalize(final_score)
      end
    end
 
  end
end
require 'monitor'
require 'decorators/decorator'
require 'decorators/list_thread_safe_decorator'
 
module Decorators
  class MatchListThreadSafeDecorator < ListThreadSafeDecorator
    include MonitorMixin
 
    def initialize(decorated)
      old_match_decorator = decorated.item_decorator
      decorated.item_decorator = lambda do |match|
        MatchThreadSafeDecorator.new( old_match_decorator.call(match) )
      end
      super
    end
 
  end
end
require 'yaml'
require 'decorators/decorator'
 
module Decorators
  class MatchPersistanceDecorator < Decorator
 
    attr_accessor :my_match_list_persistente
 
    def make_bet(score, user)
      return_object = @decorated.make_bet(score, user)
      @my_match_list_persistente.save
      return_object
    end
 
    def finalize(final_score)
      return_object = @decorated.finalize(final_score)
      @my_match_list_persistente.save
      return_object
    end
 
  end
end
require 'yaml'
require 'decorators/decorator'
require 'decorators/list_persistance_decorator'
 
module Decorators
  class MatchListPersistanceDecorator < ListPersistanceDecorator
    def initialize(decorated, filename)
      old_match_decorator = decorated.item_decorator
      decorated.item_decorator = lambda do |match|
        MatchPersistanceDecorator.new( old_match_decorator.call(match) )
      end
      super(decorated, filename)
    end
 
    def add(*args)
      obj = @decorated.add(*args) #obj é um match decorado
      obj.my_match_list_persistente = self
      save
      obj
    end
 
    def list_current
      list = @decorated.reject { |m| m.finalized? }
      list.each { |obj| obj.my_match_list_persistente = self }
      list
    end
 
    def list_finalized
      list = @decorated.find_all { |m| m.finalized? }
      list.each { |obj| obj.my_match_list_persistente = self }
      list
    end
 
    def find_by_name(name)
      obj = @decorated.find{ |match| match.name == name }
      obj.my_match_list_persistente = self
      obj
    end
 
  end
end

O passo seguinte foi a criação das classes MatchListBuilder e UserListBuilder, que basicamente criam e decoram suas List's respectivas. Seguindo as intruções do lab e das aulas, temos a seguir as duas classes Builder:

require 'match_list'
require 'decorators/decorator'
require 'decorators/match_list_persistance_decorator'
require 'decorators/match_list_logger_decorator'
require 'decorators/match_list_thread_safe_decorator'
 
  class MatchListBuilder  
 
    attr_writer :filename  
 
    def initialize
      @match_list = MatchList.new
      @added_persistance = false
    end
 
    def match_list
      @match_list.load_list if @added_persistance
      @match_list
    end
 
    def add_persistance
      @match_list = MatchListPersistanceDecorator.new(@match_list, @filename)
      @added_persistance = true
    end
 
    def add_logging
      @match_list = MatchListLoggerDecorator.new(@match_list)
    end
 
    def add_thread_safety
      @match_list = MatchListThreadSafeDecorator.new(@match_list)
    end
 
    def add_all
      add_persistance
      add_logging
      add_thread_safety
    end
 
  end
require 'user_list'
require 'decorators/decorator'
require 'decorators/list_persistance_decorator'
require 'decorators/user_list_logger_decorator'
require 'decorators/list_thread_safe_decorator'
 
  class UserListBuilder  
    attr_writer :filename
 
    def initialize
      @user_list = UserList.new
      @added_persistance = false
    end
 
    def user_list
      @user_list.load_list if @added_persistance
      @user_list
    end
 
    def add_persistance
      @user_list = ListPersistanceDecorator.new(@user_list, @filename)
      @added_persistance = true
    end
 
    def add_logging
      @user_list = UserListLoggerDecorator.new(@user_list)
    end
 
    def add_thread_safety
      @user_list = ListThreadSafeDecorator.new(@user_list)
    end
 
    def add_all
      add_persistance
      add_logging
      add_thread_safety
    end
 
  end

Abaixo, rodamos o teste acrescentando as classes acima e modificando o integration_test.rb para utilizar MatchListThreadSafeDecorator no lugar de ListThreadSafeDecorador:

lab5-fig01.JPG

Criados os decorators e builders, passamos agora à parte deste lab feita em dupla, que é a elaboração do método main, que deverá utilizar os builder criados para decorar as MatchLists e UserLists criadas, para que estas possam ter suas informações persistidas, sejam Thread Safety e mandem informações para um arquivo de log. Para armazenar essas informações, será utilizado o arquivo yml. Desta forma, criamos uma classe RequestReader qie seria responsável por ler do console (input e output padrões, que serão os utilizados nesta altura do lab) a entrada já no formato yaml (como exemplificado nas intrções deste lab). Esta entrada será constituída de uma operação (cujos nomes são definidos pela dupla) e de parâmetros, cuja quantidade varia de acordo com a operação escolhida. Após ler essa entrada corretamente, o objeto da classe RequestReader terá formado um hash (batizado de hash requisicao no exemplo das instruções desse lab) e, a partir dele, será possível realizar uma das operações pré-definidas, gerar a saída no console para o usuário (em formato yaml) e armazenar as possíveis modificações num arquivo .yml (para nossa aplicação, serão os arquivos match_list.yml e user_list.yml).
Seguem abaixo as operações definidas, bem como os parâmetros requisitados:

Criar Usuario
-> operação create_user
-> parãmetros nome do usuário, senha

Autenticar Usuario(Deve ser feita antes de qualquer uma das operações a seguir. Se for mal sucedida, encera a aplicação. Bem sucedida, define qual o usuário para o qual as operações seguintes serão realizadas)
-> operação login_user
-> parâmetros nome do usuário, senha

Listar partidas atuais (retornando uma lista de nomes de partidas com algumas informações básicas a respeito de cada uma).
-> operação list_current_matches

Listar partidas encerradas (semelhante a anterior)
-> operação list_finalized_matches

Informações detalhadas de uma partida (apostadores, ganhadores)
-> operação show_match
-> parâmetros nome da partida

Listar informações das minhas apostas (vencedoras, em andamento, finalizadas)
-> operação show_my_bets

Fazer aposta em uma partida
-> operação make_bet
-> parâmetros nome da partida, placar

Encerrar a conexão
-> operação close_connection

OBS: Por algum motivo (que não recordo agora) o método do arquivo main.rb chama-se main_method em vez de main. Mas isso não afeta o funcionamento do programa.

Tendo definido o protocolo yaml e o nome das operações, pudemos prosseguir com a implementação da classe RequestReader, para que pudesse ler as operações acima descritas e realizá-las adequadamente. Devido à proximidade das férias, não foi possível para a nossa dupla terminar esta classe antes de viajarmos, mas conseguimos deixar um esboço bem encaminhado, para que pudéssemos completar os detalhes mais adiante. Criamos também novas classes printer, que vimos que seriam úteis, tendo como base os printers dados como exemplo.

Tendo comlpetado essa parte, a última parte é fazer o arquivo server.rb, que segue abaixo (apenas com algumas alterações, visto que o programa começará a rodar a partir dele, pelo comando "ruby server.rb"):

require 'socket'
require 'initialize'
require 'user_list_builder'
require 'main'
 
require 'logger'
$logger = Logger.new("bolao.log")
$logger.level = Logger::INFO
 
class String
  def to_score
    self.strip.gsub(/\s+/, '').downcase
  end
end
 
ml_builder  = MatchListBuilder.new     
ml_builder.filename = "match_list.yml"     
ml_builder.add_all     
$match_list = ml_builder.match_list     
#~ $match_list.add("atletico x santos", Time.now + 50000)
#~ $match_list.add("palmeiras x flamengo", Time.now + 300000)
#~ $match_list.add("santos x palmeiras", Time.now + 700000)
#~ $match_list.add("santos x cruzeiro", Time.now - 200000)
#~ match = $match_list.add("flamengo x cruzeiro", Time.now - 500000)
#~ match.finalize("3x 2")
#~ $match_list.find_by_name("palmeiras x flamengo").finalize("2 x 4")
puts "match_list construida com sucesso"         
ul_builder = UserListBuilder.new     
ul_builder.filename = "user_list.yml"     
ul_builder.add_all     
$user_list = ul_builder.user_list         
puts "listas criadas"
 
server = TCPServer.new(4344)  # 4344 � a porta de conex�o de rede usada
                                                    # utilize de prefer�ncia um n�mero acima de 
                                                    # 2000 (e menor que 64000).
 
while ( io_socket = server.accept ) #aguarda at� que conex�o seja recebida, retornando um TCPSocket com a conex�o.
  # dispare uma nova thread e utilize io_socket para efetuar a entrada e sa�da do m�todo main, de dentro desta thread
 
 Thread.new do 
    puts "chamou main_method"
    main_method(io_socket, io_socket)
  end  
  #io_socket deve ser utilizado tanto para sa�da quanto para entrada, portanto � passado como par�metro in e par�metro out de main.
end

O código acima exibirá algumas mensagens que não são necessárias (e não influenciam no funcionamento do projeto), mas que ajudam a ter certeza de que as requisições estão sendo atendidas devidamente (foram bastante úteis na hora de testar e descobrir os erros que apareceram).
Por algum motivo (que não consegui identificar), o programa não estava recohecendo a variável $logger global, definida em initialize.rb. O require 'initialize' colocado em server.rb parave não ter adiantado, por isso, reescrevi o trecho de código que estava em initialize.rb (e ele passou a funcionar depois disso). Preferi fazer isso, para poder seguir adiante com os testes e terminar o lab5, pois acho que o motivo não deve ser nada muito complexo, se comparado ao restante do trabalho. A seguir, temos uma sequência de testes, mostrando as requisições feitas utilizando o protocolo adotado, e a resposta a essa requisição, tanto no putty (programa para conexão telnet), como no prompt de comando, através das mensagens auxiliares acrescentadas:

Rodando o servidor:

lab5-fig02.JPG

Conectando no servidor, pelo port dado:

lab5-fig03.JPG

Fazendo uma requisição de login (para um usuário já criado). O protocolo é digitado na tela gerada pelo putty, e, após digitarmos FIM e darmos enter, vemos a resposta imediata no prompt de comando, indicando que a operação foi bem sucedida. A linha no prompt que mostra main_method indica que a nova Thread foi criada para atender à nova conexão:

lab5-fig04.JPG

Requisição para mostrar a partida palmeiras x flamengo:

lab5-fig05.JPG

Requisição para mostar as partidas em andamento. Ao realizar esses testes, escrevi por engano show_current_matches no lugar da operação list_current_matches. A resposta no prompt de comando foi a linha indicando operação nao reconhecida. Em seguida, foi feita a requisição com o nome correto da operação:

lab5-fig06.JPG

Fazendo uma segunda conexão com o servidor:

lab5-fig07.JPG

As demais operações são análogas as apresentadas acima.
Foram acrescentados ainda as operações show_match_holders e show_match_winners, e o printer yaml_holders_printer. Eles não foram planejados para este lab5, mas percebi que seriam úteis para o lab6, então, implementei eles, inspirado nas operações e printers já feitos. Abaixo, um link com o projeto compactado:

Lab5

Conclusão

Este lab explorou diversos conceitos, padrões de projeto, dentre outros tópicos vistos em aula. Devido a não trivialidade em mostrar alguns desses tópicos funcionando, e pela quantidade de tópicos desse tipo contidas neste lab, o lab5 ficou meio extenso. Sem contar os erros que vão normalmente vão aparecendo, e o tempo que se pode levar, dependendo do erro. Acho que pela falta de um domínio maior da linguagem, foi perdido muito tempo na procura de erros que não eram tão complexos (como problemas com os requires).
Isso pode ser resultado do fato de as aulas de Ruby, por terem ficado nas últimas semanas, terem sido um pouco aceleradas, de modo que foi visto muita coisa e não foi possível ter uma absorção muito boa.
Por outro lado, o aprendizado com este lab foi bastante válido, mostrando o uso de protocolos, persistência, thread safety, multi-thread, pois, depois de completar o lab, temos esses conceitos funcionando na prática, numa aplicação que poderia, talvez com algumas melhorias, poderia estar sendo utilizada não apenas para fins acadêmicos. Então, foi uma experiência bastante válida, nesse sentido.

Add a New Comment
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License